ImsCall.java revision 95d563ed2a215d67a4f789c3ebb5a526fb95cc7f
1/*
2 * Copyright (c) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.ims;
18
19import com.android.internal.R;
20
21import java.util.ArrayList;
22import java.util.Iterator;
23import java.util.List;
24import java.util.Map.Entry;
25import java.util.Set;
26
27import android.content.Context;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.Message;
31import android.telecom.ConferenceParticipant;
32import android.telecom.Connection;
33import java.util.Objects;
34import android.util.Log;
35
36import com.android.ims.internal.ICall;
37import com.android.ims.internal.ImsCallSession;
38import com.android.ims.internal.ImsStreamMediaSession;
39import com.android.internal.annotations.VisibleForTesting;
40
41/**
42 * Handles an IMS voice / video call over LTE. You can instantiate this class with
43 * {@link ImsManager}.
44 *
45 * @hide
46 */
47public class ImsCall implements ICall {
48    // Mode of USSD message
49    public static final int USSD_MODE_NOTIFY = 0;
50    public static final int USSD_MODE_REQUEST = 1;
51
52    private static final String TAG = "ImsCall";
53
54    // This flag is meant to be used as a debugging tool to quickly see all logs
55    // regardless of the actual log level set on this component.
56    private static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
57
58    // We will log messages guarded by these flags at the info level. If logging is required
59    // to occur at (and only at) a particular log level, please use the logd, logv and loge
60    // functions as those will not be affected by the value of FORCE_DEBUG at all.
61    // Otherwise, anything guarded by these flags will be logged at the info level since that
62    // level allows those statements ot be logged by default which supports the workflow of
63    // setting FORCE_DEBUG and knowing these logs will show up regardless of the actual log
64    // level of this component.
65    private static final boolean DBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.DEBUG);
66    private static final boolean VDBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.VERBOSE);
67    // This is a special flag that is used only to highlight specific log around bringing
68    // up and tearing down conference calls. At times, these errors are transient and hard to
69    // reproduce so we need to capture this information the first time.
70    // TODO: Set this flag to FORCE_DEBUG once the new conference call logic gets more mileage
71    // across different IMS implementations.
72    private static final boolean CONF_DBG = true;
73
74    private List<ConferenceParticipant> mConferenceParticipants;
75    /**
76     * Listener for events relating to an IMS call, such as when a call is being
77     * received ("on ringing") or a call is outgoing ("on calling").
78     * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
79     */
80    public static class Listener {
81        /**
82         * Called when a request is sent out to initiate a new call
83         * and 1xx response is received from the network.
84         * The default implementation calls {@link #onCallStateChanged}.
85         *
86         * @param call the call object that carries out the IMS call
87         */
88        public void onCallProgressing(ImsCall call) {
89            onCallStateChanged(call);
90        }
91
92        /**
93         * Called when the call is established.
94         * The default implementation calls {@link #onCallStateChanged}.
95         *
96         * @param call the call object that carries out the IMS call
97         */
98        public void onCallStarted(ImsCall call) {
99            onCallStateChanged(call);
100        }
101
102        /**
103         * Called when the call setup is failed.
104         * The default implementation calls {@link #onCallError}.
105         *
106         * @param call the call object that carries out the IMS call
107         * @param reasonInfo detailed reason of the call setup failure
108         */
109        public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
110            onCallError(call, reasonInfo);
111        }
112
113        /**
114         * Called when the call is terminated.
115         * The default implementation calls {@link #onCallStateChanged}.
116         *
117         * @param call the call object that carries out the IMS call
118         * @param reasonInfo detailed reason of the call termination
119         */
120        public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
121            // Store the call termination reason
122
123            onCallStateChanged(call);
124        }
125
126        /**
127         * Called when the call is in hold.
128         * The default implementation calls {@link #onCallStateChanged}.
129         *
130         * @param call the call object that carries out the IMS call
131         */
132        public void onCallHeld(ImsCall call) {
133            onCallStateChanged(call);
134        }
135
136        /**
137         * Called when the call hold is failed.
138         * The default implementation calls {@link #onCallError}.
139         *
140         * @param call the call object that carries out the IMS call
141         * @param reasonInfo detailed reason of the call hold failure
142         */
143        public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
144            onCallError(call, reasonInfo);
145        }
146
147        /**
148         * Called when the call hold is received from the remote user.
149         * The default implementation calls {@link #onCallStateChanged}.
150         *
151         * @param call the call object that carries out the IMS call
152         */
153        public void onCallHoldReceived(ImsCall call) {
154            onCallStateChanged(call);
155        }
156
157        /**
158         * Called when the call is in call.
159         * The default implementation calls {@link #onCallStateChanged}.
160         *
161         * @param call the call object that carries out the IMS call
162         */
163        public void onCallResumed(ImsCall call) {
164            onCallStateChanged(call);
165        }
166
167        /**
168         * Called when the call resume is failed.
169         * The default implementation calls {@link #onCallError}.
170         *
171         * @param call the call object that carries out the IMS call
172         * @param reasonInfo detailed reason of the call resume failure
173         */
174        public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
175            onCallError(call, reasonInfo);
176        }
177
178        /**
179         * Called when the call resume is received from the remote user.
180         * The default implementation calls {@link #onCallStateChanged}.
181         *
182         * @param call the call object that carries out the IMS call
183         */
184        public void onCallResumeReceived(ImsCall call) {
185            onCallStateChanged(call);
186        }
187
188        /**
189         * Called when the call is in call.
190         * The default implementation calls {@link #onCallStateChanged}.
191         *
192         * @param call the call object that carries out the active IMS call
193         * @param peerCall the call object that carries out the held IMS call
194         * @param swapCalls {@code true} if the foreground and background calls should be swapped
195         *                              now that the merge has completed.
196         */
197        public void onCallMerged(ImsCall call, ImsCall peerCall, boolean swapCalls) {
198            onCallStateChanged(call);
199        }
200
201        /**
202         * Called when the call merge is failed.
203         * The default implementation calls {@link #onCallError}.
204         *
205         * @param call the call object that carries out the IMS call
206         * @param reasonInfo detailed reason of the call merge failure
207         */
208        public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
209            onCallError(call, reasonInfo);
210        }
211
212        /**
213         * Called when the call is updated (except for hold/unhold).
214         * The default implementation calls {@link #onCallStateChanged}.
215         *
216         * @param call the call object that carries out the IMS call
217         */
218        public void onCallUpdated(ImsCall call) {
219            onCallStateChanged(call);
220        }
221
222        /**
223         * Called when the call update is failed.
224         * The default implementation calls {@link #onCallError}.
225         *
226         * @param call the call object that carries out the IMS call
227         * @param reasonInfo detailed reason of the call update failure
228         */
229        public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
230            onCallError(call, reasonInfo);
231        }
232
233        /**
234         * Called when the call update is received from the remote user.
235         *
236         * @param call the call object that carries out the IMS call
237         */
238        public void onCallUpdateReceived(ImsCall call) {
239            // no-op
240        }
241
242        /**
243         * Called when the call is extended to the conference call.
244         * The default implementation calls {@link #onCallStateChanged}.
245         *
246         * @param call the call object that carries out the IMS call
247         * @param newCall the call object that is extended to the conference from the active call
248         */
249        public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
250            onCallStateChanged(call);
251        }
252
253        /**
254         * Called when the conference extension is failed.
255         * The default implementation calls {@link #onCallError}.
256         *
257         * @param call the call object that carries out the IMS call
258         * @param reasonInfo detailed reason of the conference extension failure
259         */
260        public void onCallConferenceExtendFailed(ImsCall call,
261                ImsReasonInfo reasonInfo) {
262            onCallError(call, reasonInfo);
263        }
264
265        /**
266         * Called when the conference extension is received from the remote user.
267         *
268         * @param call the call object that carries out the IMS call
269         * @param newCall the call object that is extended to the conference from the active call
270         */
271        public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
272            onCallStateChanged(call);
273        }
274
275        /**
276         * Called when the invitation request of the participants is delivered to
277         * the conference server.
278         *
279         * @param call the call object that carries out the IMS call
280         */
281        public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
282            // no-op
283        }
284
285        /**
286         * Called when the invitation request of the participants is failed.
287         *
288         * @param call the call object that carries out the IMS call
289         * @param reasonInfo detailed reason of the conference invitation failure
290         */
291        public void onCallInviteParticipantsRequestFailed(ImsCall call,
292                ImsReasonInfo reasonInfo) {
293            // no-op
294        }
295
296        /**
297         * Called when the removal request of the participants is delivered to
298         * the conference server.
299         *
300         * @param call the call object that carries out the IMS call
301         */
302        public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
303            // no-op
304        }
305
306        /**
307         * Called when the removal request of the participants is failed.
308         *
309         * @param call the call object that carries out the IMS call
310         * @param reasonInfo detailed reason of the conference removal failure
311         */
312        public void onCallRemoveParticipantsRequestFailed(ImsCall call,
313                ImsReasonInfo reasonInfo) {
314            // no-op
315        }
316
317        /**
318         * Called when the conference state is updated.
319         *
320         * @param call the call object that carries out the IMS call
321         * @param state state of the participant who is participated in the conference call
322         */
323        public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
324            // no-op
325        }
326
327        /**
328         * Called when the state of IMS conference participant(s) has changed.
329         *
330         * @param call the call object that carries out the IMS call.
331         * @param participants the participant(s) and their new state information.
332         */
333        public void onConferenceParticipantsStateChanged(ImsCall call,
334                List<ConferenceParticipant> participants) {
335            // no-op
336        }
337
338        /**
339         * Called when the USSD message is received from the network.
340         *
341         * @param mode mode of the USSD message (REQUEST / NOTIFY)
342         * @param ussdMessage USSD message
343         */
344        public void onCallUssdMessageReceived(ImsCall call,
345                int mode, String ussdMessage) {
346            // no-op
347        }
348
349        /**
350         * Called when an error occurs. The default implementation is no op.
351         * overridden. The default implementation is no op. Error events are
352         * not re-directed to this callback and are handled in {@link #onCallError}.
353         *
354         * @param call the call object that carries out the IMS call
355         * @param reasonInfo detailed reason of this error
356         * @see ImsReasonInfo
357         */
358        public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
359            // no-op
360        }
361
362        /**
363         * Called when an event occurs and the corresponding callback is not
364         * overridden. The default implementation is no op. Error events are
365         * not re-directed to this callback and are handled in {@link #onCallError}.
366         *
367         * @param call the call object that carries out the IMS call
368         */
369        public void onCallStateChanged(ImsCall call) {
370            // no-op
371        }
372
373        /**
374         * Called when the call moves the hold state to the conversation state.
375         * For example, when merging the active & hold call, the state of all the hold call
376         * will be changed from hold state to conversation state.
377         * This callback method can be invoked even though the application does not trigger
378         * any operations.
379         *
380         * @param call the call object that carries out the IMS call
381         * @param state the detailed state of call state changes;
382         *      Refer to CALL_STATE_* in {@link ImsCall}
383         */
384        public void onCallStateChanged(ImsCall call, int state) {
385            // no-op
386        }
387
388        /**
389         * Called when TTY mode of remote party changed
390         *
391         * @param call the call object that carries out the IMS call
392         * @param mode TTY mode of remote party
393         */
394        public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
395            // no-op
396        }
397
398        /**
399         * Called when handover occurs from one access technology to another.
400         *
401         * @param imsCall ImsCall object
402         * @param srcAccessTech original access technology
403         * @param targetAccessTech new access technology
404         * @param reasonInfo
405         */
406        public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
407            ImsReasonInfo reasonInfo) {
408        }
409
410        /**
411         * Called when handover from one access technology to another fails.
412         *
413         * @param imsCall call that failed the handover.
414         * @param srcAccessTech original access technology
415         * @param targetAccessTech new access technology
416         * @param reasonInfo
417         */
418        public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
419            ImsReasonInfo reasonInfo) {
420        }
421
422        /**
423         * Notifies of a change to the multiparty state for this {@code ImsCall}.
424         *
425         * @param imsCall The IMS call.
426         * @param isMultiParty {@code true} if the call became multiparty, {@code false}
427         *      otherwise.
428         */
429        public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
430        }
431    }
432
433    // List of update operation for IMS call control
434    private static final int UPDATE_NONE = 0;
435    private static final int UPDATE_HOLD = 1;
436    private static final int UPDATE_HOLD_MERGE = 2;
437    private static final int UPDATE_RESUME = 3;
438    private static final int UPDATE_MERGE = 4;
439    private static final int UPDATE_EXTEND_TO_CONFERENCE = 5;
440    private static final int UPDATE_UNSPECIFIED = 6;
441
442    // For synchronization of private variables
443    private Object mLockObj = new Object();
444    private Context mContext;
445
446    // true if the call is established & in the conversation state
447    private boolean mInCall = false;
448    // true if the call is on hold
449    // If it is triggered by the local, mute the call. Otherwise, play local hold tone
450    // or network generated media.
451    private boolean mHold = false;
452    // true if the call is on mute
453    private boolean mMute = false;
454    // It contains the exclusive call update request. Refer to UPDATE_*.
455    private int mUpdateRequest = UPDATE_NONE;
456
457    private ImsCall.Listener mListener = null;
458
459    // When merging two calls together, the "peer" call that will merge into this call.
460    private ImsCall mMergePeer = null;
461    // When merging two calls together, the "host" call we are merging into.
462    private ImsCall mMergeHost = null;
463
464    // True if Conference request was initiated by
465    // Foreground Conference call else it will be false
466    private boolean mMergeRequestedByConference = false;
467    // Wrapper call session to interworking the IMS service (server).
468    private ImsCallSession mSession = null;
469    // Call profile of the current session.
470    // It can be changed at anytime when the call is updated.
471    private ImsCallProfile mCallProfile = null;
472    // Call profile to be updated after the application's action (accept/reject)
473    // to the call update. After the application's action (accept/reject) is done,
474    // it will be set to null.
475    private ImsCallProfile mProposedCallProfile = null;
476    private ImsReasonInfo mLastReasonInfo = null;
477
478    // Media session to control media (audio/video) operations for an IMS call
479    private ImsStreamMediaSession mMediaSession = null;
480
481    // The temporary ImsCallSession that could represent the merged call once
482    // we receive notification that the merge was successful.
483    private ImsCallSession mTransientConferenceSession = null;
484    // While a merge is progressing, we bury any session termination requests
485    // made on the original ImsCallSession until we have closure on the merge request
486    // If the request ultimately fails, we need to act on the termination request
487    // that we buried temporarily. We do this because we feel that timing issues could
488    // cause the termination request to occur just because the merge is succeeding.
489    private boolean mSessionEndDuringMerge = false;
490    // Just like mSessionEndDuringMerge, we need to keep track of the reason why the
491    // termination request was made on the original session in case we need to act
492    // on it in the case of a merge failure.
493    private ImsReasonInfo mSessionEndDuringMergeReasonInfo = null;
494    // This flag is used to indicate if this ImsCall was merged into a conference
495    // or not.  It is used primarily to determine if a disconnect sound should
496    // be heard when the call is terminated.
497    private boolean mIsMerged = false;
498    // If true, this flag means that this ImsCall is in the process of merging
499    // into a conference but it does not yet have closure on if it was
500    // actually added to the conference or not. false implies that it either
501    // is not part of a merging conference or already knows if it was
502    // successfully added.
503    private boolean mCallSessionMergePending = false;
504
505    /**
506     * If {@code true}, this flag indicates that a request to terminate the call was made by
507     * Telephony (could be from the user or some internal telephony logic)
508     * and that when we receive a {@link #processCallTerminated(ImsReasonInfo)} callback from the
509     * radio indicating that the call was terminated, we should override any burying of the
510     * termination due to an ongoing conference merge.
511     */
512    private boolean mTerminationRequestPending = false;
513
514    /**
515     * For multi-party IMS calls (e.g. conferences), determines if this {@link ImsCall} is the one
516     * hosting the call.  This is used to distinguish between a situation where an {@link ImsCall}
517     * is {@link #isMultiparty()} because calls were merged on the device, and a situation where
518     * an {@link ImsCall} is {@link #isMultiparty()} because it is a member of a conference started
519     * on another device.
520     * <p>
521     * When {@code true}, this {@link ImsCall} is is the origin of the conference call.
522     * When {@code false}, this {@link ImsCall} is a member of a conference started on another
523     * device.
524     */
525    private boolean mIsConferenceHost = false;
526
527    /**
528     * Create an IMS call object.
529     *
530     * @param context the context for accessing system services
531     * @param profile the call profile to make/take a call
532     */
533    public ImsCall(Context context, ImsCallProfile profile) {
534        mContext = context;
535        mCallProfile = profile;
536    }
537
538    /**
539     * Closes this object. This object is not usable after being closed.
540     */
541    @Override
542    public void close() {
543        synchronized(mLockObj) {
544            if (mSession != null) {
545                mSession.close();
546                mSession = null;
547            } else {
548                logi("close :: Cannot close Null call session!");
549            }
550
551            mCallProfile = null;
552            mProposedCallProfile = null;
553            mLastReasonInfo = null;
554            mMediaSession = null;
555        }
556    }
557
558    /**
559     * Checks if the call has a same remote user identity or not.
560     *
561     * @param userId the remote user identity
562     * @return true if the remote user identity is equal; otherwise, false
563     */
564    @Override
565    public boolean checkIfRemoteUserIsSame(String userId) {
566        if (userId == null) {
567            return false;
568        }
569
570        return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
571    }
572
573    /**
574     * Checks if the call is equal or not.
575     *
576     * @param call the call to be compared
577     * @return true if the call is equal; otherwise, false
578     */
579    @Override
580    public boolean equalsTo(ICall call) {
581        if (call == null) {
582            return false;
583        }
584
585        if (call instanceof ImsCall) {
586            return this.equals(call);
587        }
588
589        return false;
590    }
591
592    public static boolean isSessionAlive(ImsCallSession session) {
593        return session != null && session.isAlive();
594    }
595
596    /**
597     * Gets the negotiated (local & remote) call profile.
598     *
599     * @return a {@link ImsCallProfile} object that has the negotiated call profile
600     */
601    public ImsCallProfile getCallProfile() {
602        synchronized(mLockObj) {
603            return mCallProfile;
604        }
605    }
606
607    /**
608     * Gets the local call profile (local capabilities).
609     *
610     * @return a {@link ImsCallProfile} object that has the local call profile
611     */
612    public ImsCallProfile getLocalCallProfile() throws ImsException {
613        synchronized(mLockObj) {
614            if (mSession == null) {
615                throw new ImsException("No call session",
616                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
617            }
618
619            try {
620                return mSession.getLocalCallProfile();
621            } catch (Throwable t) {
622                loge("getLocalCallProfile :: ", t);
623                throw new ImsException("getLocalCallProfile()", t, 0);
624            }
625        }
626    }
627
628    /**
629     * Gets the remote call profile (remote capabilities).
630     *
631     * @return a {@link ImsCallProfile} object that has the remote call profile
632     */
633    public ImsCallProfile getRemoteCallProfile() throws ImsException {
634        synchronized(mLockObj) {
635            if (mSession == null) {
636                throw new ImsException("No call session",
637                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
638            }
639
640            try {
641                return mSession.getRemoteCallProfile();
642            } catch (Throwable t) {
643                loge("getRemoteCallProfile :: ", t);
644                throw new ImsException("getRemoteCallProfile()", t, 0);
645            }
646        }
647    }
648
649    /**
650     * Gets the call profile proposed by the local/remote user.
651     *
652     * @return a {@link ImsCallProfile} object that has the proposed call profile
653     */
654    public ImsCallProfile getProposedCallProfile() {
655        synchronized(mLockObj) {
656            if (!isInCall()) {
657                return null;
658            }
659
660            return mProposedCallProfile;
661        }
662    }
663
664    /**
665     * Gets the list of conference participants currently
666     * associated with this call.
667     *
668     * @return The list of conference participants.
669     */
670    public List<ConferenceParticipant> getConferenceParticipants() {
671        synchronized(mLockObj) {
672            logi("getConferenceParticipants :: mConferenceParticipants"
673                    + mConferenceParticipants);
674            return mConferenceParticipants;
675        }
676    }
677
678    /**
679     * Gets the state of the {@link ImsCallSession} that carries this call.
680     * The value returned must be one of the states in {@link ImsCallSession#State}.
681     *
682     * @return the session state
683     */
684    public int getState() {
685        synchronized(mLockObj) {
686            if (mSession == null) {
687                return ImsCallSession.State.IDLE;
688            }
689
690            return mSession.getState();
691        }
692    }
693
694    /**
695     * Gets the {@link ImsCallSession} that carries this call.
696     *
697     * @return the session object that carries this call
698     * @hide
699     */
700    public ImsCallSession getCallSession() {
701        synchronized(mLockObj) {
702            return mSession;
703        }
704    }
705
706    /**
707     * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
708     * Almost interface APIs are for the VT (Video Telephony).
709     *
710     * @return the media session object that handles the media operation of this call
711     * @hide
712     */
713    public ImsStreamMediaSession getMediaSession() {
714        synchronized(mLockObj) {
715            return mMediaSession;
716        }
717    }
718
719    /**
720     * Gets the specified property of this call.
721     *
722     * @param name key to get the extra call information defined in {@link ImsCallProfile}
723     * @return the extra call information as string
724     */
725    public String getCallExtra(String name) throws ImsException {
726        // Lookup the cache
727
728        synchronized(mLockObj) {
729            // If not found, try to get the property from the remote
730            if (mSession == null) {
731                throw new ImsException("No call session",
732                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
733            }
734
735            try {
736                return mSession.getProperty(name);
737            } catch (Throwable t) {
738                loge("getCallExtra :: ", t);
739                throw new ImsException("getCallExtra()", t, 0);
740            }
741        }
742    }
743
744    /**
745     * Gets the last reason information when the call is not established, cancelled or terminated.
746     *
747     * @return the last reason information
748     */
749    public ImsReasonInfo getLastReasonInfo() {
750        synchronized(mLockObj) {
751            return mLastReasonInfo;
752        }
753    }
754
755    /**
756     * Checks if the call has a pending update operation.
757     *
758     * @return true if the call has a pending update operation
759     */
760    public boolean hasPendingUpdate() {
761        synchronized(mLockObj) {
762            return (mUpdateRequest != UPDATE_NONE);
763        }
764    }
765
766    /**
767     * Checks if the call is pending a hold operation.
768     *
769     * @return true if the call is pending a hold operation.
770     */
771    public boolean isPendingHold() {
772        synchronized(mLockObj) {
773            return (mUpdateRequest == UPDATE_HOLD);
774        }
775    }
776
777    /**
778     * Checks if the call is established.
779     *
780     * @return true if the call is established
781     */
782    public boolean isInCall() {
783        synchronized(mLockObj) {
784            return mInCall;
785        }
786    }
787
788    /**
789     * Checks if the call is muted.
790     *
791     * @return true if the call is muted
792     */
793    public boolean isMuted() {
794        synchronized(mLockObj) {
795            return mMute;
796        }
797    }
798
799    /**
800     * Checks if the call is on hold.
801     *
802     * @return true if the call is on hold
803     */
804    public boolean isOnHold() {
805        synchronized(mLockObj) {
806            return mHold;
807        }
808    }
809
810    /**
811     * Determines if the call is a multiparty call.
812     *
813     * @return {@code True} if the call is a multiparty call.
814     */
815    public boolean isMultiparty() {
816        synchronized(mLockObj) {
817            if (mSession == null) {
818                return false;
819            }
820
821            return mSession.isMultiparty();
822        }
823    }
824
825    /**
826     * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
827     * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
828     * {@link ImsCall} is a member of a conference hosted on another device.
829     *
830     * @return {@code true} if this call is the origin of the conference call it is a member of,
831     *      {@code false} otherwise.
832     */
833    public boolean isConferenceHost() {
834        synchronized(mLockObj) {
835            return isMultiparty() && mIsConferenceHost;
836        }
837    }
838
839    /**
840     * Marks whether an IMS call is merged. This should be set {@code true} when the call merges
841     * into a conference.
842     *
843     * @param isMerged Whether the call is merged.
844     */
845    public void setIsMerged(boolean isMerged) {
846        mIsMerged = isMerged;
847    }
848
849    /**
850     * @return {@code true} if the call recently merged into a conference call.
851     */
852    public boolean isMerged() {
853        return mIsMerged;
854    }
855
856    /**
857     * Sets the listener to listen to the IMS call events.
858     * The method calls {@link #setListener setListener(listener, false)}.
859     *
860     * @param listener to listen to the IMS call events of this object; null to remove listener
861     * @see #setListener(Listener, boolean)
862     */
863    public void setListener(ImsCall.Listener listener) {
864        setListener(listener, false);
865    }
866
867    /**
868     * Sets the listener to listen to the IMS call events.
869     * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
870     * to this method override the previous listener.
871     *
872     * @param listener to listen to the IMS call events of this object; null to remove listener
873     * @param callbackImmediately set to true if the caller wants to be called
874     *        back immediately on the current state
875     */
876    public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
877        boolean inCall;
878        boolean onHold;
879        int state;
880        ImsReasonInfo lastReasonInfo;
881
882        synchronized(mLockObj) {
883            mListener = listener;
884
885            if ((listener == null) || !callbackImmediately) {
886                return;
887            }
888
889            inCall = mInCall;
890            onHold = mHold;
891            state = getState();
892            lastReasonInfo = mLastReasonInfo;
893        }
894
895        try {
896            if (lastReasonInfo != null) {
897                listener.onCallError(this, lastReasonInfo);
898            } else if (inCall) {
899                if (onHold) {
900                    listener.onCallHeld(this);
901                } else {
902                    listener.onCallStarted(this);
903                }
904            } else {
905                switch (state) {
906                    case ImsCallSession.State.ESTABLISHING:
907                        listener.onCallProgressing(this);
908                        break;
909                    case ImsCallSession.State.TERMINATED:
910                        listener.onCallTerminated(this, lastReasonInfo);
911                        break;
912                    default:
913                        // Ignore it. There is no action in the other state.
914                        break;
915                }
916            }
917        } catch (Throwable t) {
918            loge("setListener() :: ", t);
919        }
920    }
921
922    /**
923     * Mutes or unmutes the mic for the active call.
924     *
925     * @param muted true if the call is muted, false otherwise
926     */
927    public void setMute(boolean muted) throws ImsException {
928        synchronized(mLockObj) {
929            if (mMute != muted) {
930                logi("setMute :: turning mute " + (muted ? "on" : "off"));
931                mMute = muted;
932
933                try {
934                    mSession.setMute(muted);
935                } catch (Throwable t) {
936                    loge("setMute :: ", t);
937                    throwImsException(t, 0);
938                }
939            }
940        }
941    }
942
943     /**
944      * Attaches an incoming call to this call object.
945      *
946      * @param session the session that receives the incoming call
947      * @throws ImsException if the IMS service fails to attach this object to the session
948      */
949     public void attachSession(ImsCallSession session) throws ImsException {
950         logi("attachSession :: session=" + session);
951
952         synchronized(mLockObj) {
953             mSession = session;
954
955             try {
956                 mSession.setListener(createCallSessionListener());
957             } catch (Throwable t) {
958                 loge("attachSession :: ", t);
959                 throwImsException(t, 0);
960             }
961         }
962     }
963
964    /**
965     * Initiates an IMS call with the call profile which is provided
966     * when creating a {@link ImsCall}.
967     *
968     * @param session the {@link ImsCallSession} for carrying out the call
969     * @param callee callee information to initiate an IMS call
970     * @throws ImsException if the IMS service fails to initiate the call
971     */
972    public void start(ImsCallSession session, String callee)
973            throws ImsException {
974        logi("start(1) :: session=" + session + ", callee=" + callee);
975
976        synchronized(mLockObj) {
977            mSession = session;
978
979            try {
980                session.setListener(createCallSessionListener());
981                session.start(callee, mCallProfile);
982            } catch (Throwable t) {
983                loge("start(1) :: ", t);
984                throw new ImsException("start(1)", t, 0);
985            }
986        }
987    }
988
989    /**
990     * Initiates an IMS conferenca call with the call profile which is provided
991     * when creating a {@link ImsCall}.
992     *
993     * @param session the {@link ImsCallSession} for carrying out the call
994     * @param participants participant list to initiate an IMS conference call
995     * @throws ImsException if the IMS service fails to initiate the call
996     */
997    public void start(ImsCallSession session, String[] participants)
998            throws ImsException {
999        logi("start(n) :: session=" + session + ", callee=" + participants);
1000
1001        synchronized(mLockObj) {
1002            mSession = session;
1003
1004            try {
1005                session.setListener(createCallSessionListener());
1006                session.start(participants, mCallProfile);
1007            } catch (Throwable t) {
1008                loge("start(n) :: ", t);
1009                throw new ImsException("start(n)", t, 0);
1010            }
1011        }
1012    }
1013
1014    /**
1015     * Accepts a call.
1016     *
1017     * @see Listener#onCallStarted
1018     *
1019     * @param callType The call type the user agreed to for accepting the call.
1020     * @throws ImsException if the IMS service fails to accept the call
1021     */
1022    public void accept(int callType) throws ImsException {
1023        accept(callType, new ImsStreamMediaProfile());
1024    }
1025
1026    /**
1027     * Accepts a call.
1028     *
1029     * @param callType call type to be answered in {@link ImsCallProfile}
1030     * @param profile a media profile to be answered (audio/audio & video, direction, ...)
1031     * @see Listener#onCallStarted
1032     * @throws ImsException if the IMS service fails to accept the call
1033     */
1034    public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
1035        logi("accept :: callType=" + callType + ", profile=" + profile);
1036
1037        synchronized(mLockObj) {
1038            if (mSession == null) {
1039                throw new ImsException("No call to answer",
1040                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1041            }
1042
1043            try {
1044                mSession.accept(callType, profile);
1045            } catch (Throwable t) {
1046                loge("accept :: ", t);
1047                throw new ImsException("accept()", t, 0);
1048            }
1049
1050            if (mInCall && (mProposedCallProfile != null)) {
1051                if (DBG) {
1052                    logi("accept :: call profile will be updated");
1053                }
1054
1055                mCallProfile = mProposedCallProfile;
1056                mProposedCallProfile = null;
1057            }
1058
1059            // Other call update received
1060            if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1061                mUpdateRequest = UPDATE_NONE;
1062            }
1063        }
1064    }
1065
1066    /**
1067     * Rejects a call.
1068     *
1069     * @param reason reason code to reject an incoming call
1070     * @see Listener#onCallStartFailed
1071     * @throws ImsException if the IMS service fails to reject the call
1072     */
1073    public void reject(int reason) throws ImsException {
1074        logi("reject :: reason=" + reason);
1075
1076        synchronized(mLockObj) {
1077            if (mSession != null) {
1078                mSession.reject(reason);
1079            }
1080
1081            if (mInCall && (mProposedCallProfile != null)) {
1082                if (DBG) {
1083                    logi("reject :: call profile is not updated; destroy it...");
1084                }
1085
1086                mProposedCallProfile = null;
1087            }
1088
1089            // Other call update received
1090            if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
1091                mUpdateRequest = UPDATE_NONE;
1092            }
1093        }
1094    }
1095
1096    /**
1097     * Terminates an IMS call (e.g. user initiated).
1098     *
1099     * @param reason reason code to terminate a call
1100     * @throws ImsException if the IMS service fails to terminate the call
1101     */
1102    public void terminate(int reason) throws ImsException {
1103        logi("terminate :: reason=" + reason);
1104
1105        synchronized(mLockObj) {
1106            mHold = false;
1107            mInCall = false;
1108            mTerminationRequestPending = true;
1109
1110            if (mSession != null) {
1111                // TODO: Fix the fact that user invoked call terminations during
1112                // the process of establishing a conference call needs to be handled
1113                // as a special case.
1114                // Currently, any terminations (both invoked by the user or
1115                // by the network results in a callSessionTerminated() callback
1116                // from the network.  When establishing a conference call we bury
1117                // these callbacks until we get closure on all participants of the
1118                // conference. In some situations, we will throw away the callback
1119                // (when the underlying session of the host of the new conference
1120                // is terminated) or will will unbury it when the conference has been
1121                // established, like when the peer of the new conference goes away
1122                // after the conference has been created.  The UI relies on the callback
1123                // to reflect the fact that the call is gone.
1124                // So if a user decides to terminated a call while it is merging, it
1125                // could take a long time to reflect in the UI due to the conference
1126                // processing but we should probably cancel that and just terminate
1127                // the call immediately and clean up.  This is not a huge issue right
1128                // now because we have not seen instances where establishing a
1129                // conference takes a long time (more than a second or two).
1130                mSession.terminate(reason);
1131            }
1132        }
1133    }
1134
1135
1136    /**
1137     * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
1138     *
1139     * @see Listener#onCallHeld, Listener#onCallHoldFailed
1140     * @throws ImsException if the IMS service fails to hold the call
1141     */
1142    public void hold() throws ImsException {
1143        logi("hold :: ");
1144
1145        if (isOnHold()) {
1146            if (DBG) {
1147                logi("hold :: call is already on hold");
1148            }
1149            return;
1150        }
1151
1152        synchronized(mLockObj) {
1153            if (mUpdateRequest != UPDATE_NONE) {
1154                loge("hold :: update is in progress; request=" +
1155                        updateRequestToString(mUpdateRequest));
1156                throw new ImsException("Call update is in progress",
1157                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1158            }
1159
1160            if (mSession == null) {
1161                throw new ImsException("No call session",
1162                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1163            }
1164
1165            mSession.hold(createHoldMediaProfile());
1166            // FIXME: We should update the state on the callback because that is where
1167            // we can confirm that the hold request was successful or not.
1168            mHold = true;
1169            mUpdateRequest = UPDATE_HOLD;
1170        }
1171    }
1172
1173    /**
1174     * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
1175     *
1176     * @see Listener#onCallResumed, Listener#onCallResumeFailed
1177     * @throws ImsException if the IMS service fails to resume the call
1178     */
1179    public void resume() throws ImsException {
1180        logi("resume :: ");
1181
1182        if (!isOnHold()) {
1183            if (DBG) {
1184                logi("resume :: call is not being held");
1185            }
1186            return;
1187        }
1188
1189        synchronized(mLockObj) {
1190            if (mUpdateRequest != UPDATE_NONE) {
1191                loge("resume :: update is in progress; request=" +
1192                        updateRequestToString(mUpdateRequest));
1193                throw new ImsException("Call update is in progress",
1194                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1195            }
1196
1197            if (mSession == null) {
1198                loge("resume :: ");
1199                throw new ImsException("No call session",
1200                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1201            }
1202
1203            // mHold is set to false in confirmation callback that the
1204            // ImsCall was resumed.
1205            mUpdateRequest = UPDATE_RESUME;
1206            mSession.resume(createResumeMediaProfile());
1207        }
1208    }
1209
1210    /**
1211     * Merges the active & hold call.
1212     *
1213     * @see Listener#onCallMerged, Listener#onCallMergeFailed
1214     * @throws ImsException if the IMS service fails to merge the call
1215     */
1216    private void merge() throws ImsException {
1217        logi("merge :: ");
1218
1219        synchronized(mLockObj) {
1220            if (mUpdateRequest != UPDATE_NONE) {
1221                loge("merge :: update is in progress; request=" +
1222                        updateRequestToString(mUpdateRequest));
1223                throw new ImsException("Call update is in progress",
1224                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1225            }
1226
1227            if (mSession == null) {
1228                loge("merge :: no call session");
1229                throw new ImsException("No call session",
1230                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1231            }
1232
1233            // if skipHoldBeforeMerge = true, IMS service implementation will
1234            // merge without explicitly holding the call.
1235            if (mHold || (mContext.getResources().getBoolean(
1236                    com.android.internal.R.bool.skipHoldBeforeMerge))) {
1237
1238                if (mMergePeer != null && !mMergePeer.isMultiparty() && !isMultiparty()) {
1239                    // We only set UPDATE_MERGE when we are adding the first
1240                    // calls to the Conference.  If there is already a conference
1241                    // no special handling is needed. The existing conference
1242                    // session will just go active and any other sessions will be terminated
1243                    // if needed.  There will be no merge failed callback.
1244                    // Mark both the host and peer UPDATE_MERGE to ensure both are aware that a
1245                    // merge is pending.
1246                    mUpdateRequest = UPDATE_MERGE;
1247                    mMergePeer.mUpdateRequest = UPDATE_MERGE;
1248                }
1249
1250                mSession.merge();
1251            } else {
1252                // This code basically says, we need to explicitly hold before requesting a merge
1253                // when we get the callback that the hold was successful (or failed), we should
1254                // automatically request a merge.
1255                mSession.hold(createHoldMediaProfile());
1256                mHold = true;
1257                mUpdateRequest = UPDATE_HOLD_MERGE;
1258            }
1259        }
1260    }
1261
1262    /**
1263     * Merges the active & hold call.
1264     *
1265     * @param bgCall the background (holding) call
1266     * @see Listener#onCallMerged, Listener#onCallMergeFailed
1267     * @throws ImsException if the IMS service fails to merge the call
1268     */
1269    public void merge(ImsCall bgCall) throws ImsException {
1270        logi("merge(1) :: bgImsCall=" + bgCall);
1271
1272        if (bgCall == null) {
1273            throw new ImsException("No background call",
1274                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1275        }
1276
1277        synchronized(mLockObj) {
1278            // Mark both sessions as pending merge.
1279            this.setCallSessionMergePending(true);
1280            bgCall.setCallSessionMergePending(true);
1281
1282            if ((!isMultiparty() && !bgCall.isMultiparty()) || isMultiparty()) {
1283                // If neither call is multiparty, the current call is the merge host and the bg call
1284                // is the merge peer (ie we're starting a new conference).
1285                // OR
1286                // If this call is multiparty, it is the merge host and the other call is the merge
1287                // peer.
1288                setMergePeer(bgCall);
1289            } else {
1290                // If the bg call is multiparty, it is the merge host.
1291                setMergeHost(bgCall);
1292            }
1293        }
1294
1295        if (isMultiparty()) {
1296            mMergeRequestedByConference = true;
1297        } else {
1298            logi("merge : mMergeRequestedByConference not set");
1299        }
1300        merge();
1301    }
1302
1303    /**
1304     * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1305     */
1306    public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
1307        logi("update :: callType=" + callType + ", mediaProfile=" + mediaProfile);
1308
1309        if (isOnHold()) {
1310            if (DBG) {
1311                logi("update :: call is on hold");
1312            }
1313            throw new ImsException("Not in a call to update call",
1314                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1315        }
1316
1317        synchronized(mLockObj) {
1318            if (mUpdateRequest != UPDATE_NONE) {
1319                if (DBG) {
1320                    logi("update :: update is in progress; request=" +
1321                            updateRequestToString(mUpdateRequest));
1322                }
1323                throw new ImsException("Call update is in progress",
1324                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1325            }
1326
1327            if (mSession == null) {
1328                loge("update :: ");
1329                throw new ImsException("No call session",
1330                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1331            }
1332
1333            mSession.update(callType, mediaProfile);
1334            mUpdateRequest = UPDATE_UNSPECIFIED;
1335        }
1336    }
1337
1338    /**
1339     * Extends this call (1-to-1 call) to the conference call
1340     * inviting the specified participants to.
1341     *
1342     */
1343    public void extendToConference(String[] participants) throws ImsException {
1344        logi("extendToConference ::");
1345
1346        if (isOnHold()) {
1347            if (DBG) {
1348                logi("extendToConference :: call is on hold");
1349            }
1350            throw new ImsException("Not in a call to extend a call to conference",
1351                    ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1352        }
1353
1354        synchronized(mLockObj) {
1355            if (mUpdateRequest != UPDATE_NONE) {
1356                if (CONF_DBG) {
1357                    logi("extendToConference :: update is in progress; request=" +
1358                            updateRequestToString(mUpdateRequest));
1359                }
1360                throw new ImsException("Call update is in progress",
1361                        ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1362            }
1363
1364            if (mSession == null) {
1365                loge("extendToConference :: ");
1366                throw new ImsException("No call session",
1367                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1368            }
1369
1370            mSession.extendToConference(participants);
1371            mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1372        }
1373    }
1374
1375    /**
1376     * Requests the conference server to invite an additional participants to the conference.
1377     *
1378     */
1379    public void inviteParticipants(String[] participants) throws ImsException {
1380        logi("inviteParticipants ::");
1381
1382        synchronized(mLockObj) {
1383            if (mSession == null) {
1384                loge("inviteParticipants :: ");
1385                throw new ImsException("No call session",
1386                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1387            }
1388
1389            mSession.inviteParticipants(participants);
1390        }
1391    }
1392
1393    /**
1394     * Requests the conference server to remove the specified participants from the conference.
1395     *
1396     */
1397    public void removeParticipants(String[] participants) throws ImsException {
1398        logi("removeParticipants :: session=" + mSession);
1399        synchronized(mLockObj) {
1400            if (mSession == null) {
1401                loge("removeParticipants :: ");
1402                throw new ImsException("No call session",
1403                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1404            }
1405
1406            mSession.removeParticipants(participants);
1407
1408        }
1409    }
1410
1411    /**
1412     * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1413     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1414     * and event flash to 16. Currently, event flash is not supported.
1415     *
1416     * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1417     * @param result the result message to send when done.
1418     */
1419    public void sendDtmf(char c, Message result) {
1420        logi("sendDtmf :: code=" + c);
1421
1422        synchronized(mLockObj) {
1423            if (mSession != null) {
1424                mSession.sendDtmf(c, result);
1425            }
1426        }
1427    }
1428
1429    /**
1430     * Start a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1431     * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1432     * and event flash to 16. Currently, event flash is not supported.
1433     *
1434     * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1435     */
1436    public void startDtmf(char c) {
1437        logi("startDtmf :: code=" + c);
1438
1439        synchronized(mLockObj) {
1440            if (mSession != null) {
1441                mSession.startDtmf(c);
1442            }
1443        }
1444    }
1445
1446    /**
1447     * Stop a DTMF code.
1448     */
1449    public void stopDtmf() {
1450        logi("stopDtmf :: ");
1451
1452        synchronized(mLockObj) {
1453            if (mSession != null) {
1454                mSession.stopDtmf();
1455            }
1456        }
1457    }
1458
1459    /**
1460     * Sends an USSD message.
1461     *
1462     * @param ussdMessage USSD message to send
1463     */
1464    public void sendUssd(String ussdMessage) throws ImsException {
1465        logi("sendUssd :: ussdMessage=" + ussdMessage);
1466
1467        synchronized(mLockObj) {
1468            if (mSession == null) {
1469                loge("sendUssd :: ");
1470                throw new ImsException("No call session",
1471                        ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1472            }
1473
1474            mSession.sendUssd(ussdMessage);
1475        }
1476    }
1477
1478    private void clear(ImsReasonInfo lastReasonInfo) {
1479        mInCall = false;
1480        mHold = false;
1481        mUpdateRequest = UPDATE_NONE;
1482        mLastReasonInfo = lastReasonInfo;
1483    }
1484
1485    /**
1486     * Creates an IMS call session listener.
1487     */
1488    private ImsCallSession.Listener createCallSessionListener() {
1489        return new ImsCallSessionListenerProxy();
1490    }
1491
1492    private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1493        ImsCall call = new ImsCall(mContext, profile);
1494
1495        try {
1496            call.attachSession(session);
1497        } catch (ImsException e) {
1498            if (call != null) {
1499                call.close();
1500                call = null;
1501            }
1502        }
1503
1504        // Do additional operations...
1505
1506        return call;
1507    }
1508
1509    private ImsStreamMediaProfile createHoldMediaProfile() {
1510        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1511
1512        if (mCallProfile == null) {
1513            return mediaProfile;
1514        }
1515
1516        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1517        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1518        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1519
1520        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1521            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1522        }
1523
1524        return mediaProfile;
1525    }
1526
1527    private ImsStreamMediaProfile createResumeMediaProfile() {
1528        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1529
1530        if (mCallProfile == null) {
1531            return mediaProfile;
1532        }
1533
1534        mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1535        mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1536        mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1537
1538        if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1539            mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1540        }
1541
1542        return mediaProfile;
1543    }
1544
1545    private void enforceConversationMode() {
1546        if (mInCall) {
1547            mHold = false;
1548            mUpdateRequest = UPDATE_NONE;
1549        }
1550    }
1551
1552    private void mergeInternal() {
1553        if (CONF_DBG) {
1554            logi("mergeInternal :: ");
1555        }
1556
1557        mSession.merge();
1558        mUpdateRequest = UPDATE_MERGE;
1559    }
1560
1561    private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1562        ImsCall.Listener listener = mListener;
1563        clear(reasonInfo);
1564
1565        if (listener != null) {
1566            try {
1567                listener.onCallTerminated(this, reasonInfo);
1568            } catch (Throwable t) {
1569                loge("notifyConferenceSessionTerminated :: ", t);
1570            }
1571        }
1572    }
1573
1574    private void notifyConferenceStateUpdated(ImsConferenceState state) {
1575        Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet();
1576
1577        if (participants == null) {
1578            return;
1579        }
1580
1581        Iterator<Entry<String, Bundle>> iterator = participants.iterator();
1582        mConferenceParticipants = new ArrayList<>(participants.size());
1583        while (iterator.hasNext()) {
1584            Entry<String, Bundle> entry = iterator.next();
1585
1586            String key = entry.getKey();
1587            Bundle confInfo = entry.getValue();
1588            String status = confInfo.getString(ImsConferenceState.STATUS);
1589            String user = confInfo.getString(ImsConferenceState.USER);
1590            String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT);
1591            String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1592
1593            if (CONF_DBG) {
1594                logi("notifyConferenceStateUpdated :: key=" + key +
1595                        ", status=" + status +
1596                        ", user=" + user +
1597                        ", displayName= " + displayName +
1598                        ", endpoint=" + endpoint);
1599            }
1600
1601            Uri handle = Uri.parse(user);
1602            if (endpoint == null) {
1603                endpoint = "";
1604            }
1605            Uri endpointUri = Uri.parse(endpoint);
1606            int connectionState = ImsConferenceState.getConnectionStateForStatus(status);
1607
1608            if (connectionState != Connection.STATE_DISCONNECTED) {
1609                ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle,
1610                        displayName, endpointUri, connectionState);
1611                mConferenceParticipants.add(conferenceParticipant);
1612            }
1613        }
1614
1615        if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()
1616                && mListener != null) {
1617            try {
1618                mListener.onConferenceParticipantsStateChanged(this, mConferenceParticipants);
1619            } catch (Throwable t) {
1620                loge("notifyConferenceStateUpdated :: ", t);
1621            }
1622        }
1623    }
1624
1625    /**
1626     * Perform all cleanup and notification around the termination of a session.
1627     * Note that there are 2 distinct modes of operation.  The first is when
1628     * we receive a session termination on the primary session when we are
1629     * in the processing of merging.  The second is when we are not merging anything
1630     * and the call is terminated.
1631     *
1632     * @param reasonInfo The reason for the session termination
1633     */
1634    private void processCallTerminated(ImsReasonInfo reasonInfo) {
1635        logi("processCallTerminated :: reason=" + reasonInfo + " userInitiated = " +
1636                mTerminationRequestPending);
1637
1638        ImsCall.Listener listener = null;
1639        synchronized(ImsCall.this) {
1640            // If we are in the midst of establishing a conference, we will bury the termination
1641            // until the merge has completed.  If necessary we can surface the termination at
1642            // this point.
1643            // We will also NOT bury the termination if a termination was initiated locally.
1644            if (isCallSessionMergePending() && !mTerminationRequestPending) {
1645                // Since we are in the process of a merge, this trigger means something
1646                // else because it is probably due to the merge happening vs. the
1647                // session is really terminated. Let's flag this and revisit if
1648                // the merge() ends up failing because we will need to take action on the
1649                // mSession in that case since the termination was not due to the merge
1650                // succeeding.
1651                if (CONF_DBG) {
1652                    logi("processCallTerminated :: burying termination during ongoing merge.");
1653                }
1654                mSessionEndDuringMerge = true;
1655                mSessionEndDuringMergeReasonInfo = reasonInfo;
1656                return;
1657            } else if (mTerminationRequestPending) {
1658                // Abort the merge if we receive a termination request from telephony or the user.
1659                clearMergeInfo();
1660            }
1661
1662            // If we are terminating the conference call, notify using conference listeners.
1663            if (isMultiparty()) {
1664                notifyConferenceSessionTerminated(reasonInfo);
1665                return;
1666            } else {
1667                listener = mListener;
1668                clear(reasonInfo);
1669            }
1670        }
1671
1672        if (listener != null) {
1673            try {
1674                listener.onCallTerminated(ImsCall.this, reasonInfo);
1675            } catch (Throwable t) {
1676                loge("processCallTerminated :: ", t);
1677            }
1678        }
1679    }
1680
1681    /**
1682     * This function determines if the ImsCallSession is our actual ImsCallSession or if is
1683     * the transient session used in the process of creating a conference. This function should only
1684     * be called within  callbacks that are not directly related to conference merging but might
1685     * potentially still be called on the transient ImsCallSession sent to us from
1686     * callSessionMergeStarted() when we don't really care. In those situations, we probably don't
1687     * want to take any action so we need to know that we can return early.
1688     *
1689     * @param session - The {@link ImsCallSession} that the function needs to analyze
1690     * @return true if this is the transient {@link ImsCallSession}, false otherwise.
1691     */
1692    private boolean isTransientConferenceSession(ImsCallSession session) {
1693        if (session != null && session != mSession && session == mTransientConferenceSession) {
1694            return true;
1695        }
1696        return false;
1697    }
1698
1699    private void setTransientSessionAsPrimary(ImsCallSession transientSession) {
1700        synchronized (ImsCall.this) {
1701            mSession.setListener(null);
1702            mSession = transientSession;
1703            mSession.setListener(createCallSessionListener());
1704        }
1705    }
1706
1707    private void maybeMarkPeerAsMerged() {
1708        if (!isSessionAlive(mMergePeer.mSession)) {
1709            // If the peer is dead, let's not play a disconnect sound for it when we
1710            // unbury the termination callback.
1711            logi("maybeMarkPeerAsMerged");
1712            mMergePeer.setIsMerged(true);
1713            mMergePeer.mSessionEndDuringMerge = true;
1714            mMergePeer.mSessionEndDuringMergeReasonInfo = new ImsReasonInfo(
1715                    ImsReasonInfo.CODE_UNSPECIFIED, 0,
1716                    "Call ended during conference merge process.");
1717        }
1718    }
1719
1720    /**
1721     * Checks if the merge was requested by foreground conference call
1722     *
1723     * @return true if the merge was requested by foreground conference call
1724     */
1725    public boolean isMergeRequestedByConf() {
1726        synchronized(mLockObj) {
1727            return mMergeRequestedByConference;
1728        }
1729    }
1730
1731    /**
1732     * Resets the flag which indicates merge request was sent by
1733     * foreground conference call
1734     */
1735    public void resetIsMergeRequestedByConf(boolean value) {
1736        synchronized(mLockObj) {
1737            mMergeRequestedByConference = value;
1738        }
1739    }
1740
1741    /**
1742     * Returns current ImsCallSession
1743     *
1744     * @return current session
1745     */
1746    public ImsCallSession getSession() {
1747        synchronized(mLockObj) {
1748            return mSession;
1749        }
1750    }
1751
1752    /**
1753     * We have detected that a initial conference call has been fully configured. The internal
1754     * state of both {@code ImsCall} objects need to be cleaned up to reflect the new state.
1755     * This function should only be called in the context of the merge host to simplify logic
1756     *
1757     */
1758    private void processMergeComplete() {
1759        logi("processMergeComplete :: ");
1760
1761        // The logic simplifies if we can assume that this function is only called on
1762        // the merge host.
1763        if (!isMergeHost()) {
1764            loge("processMergeComplete :: We are not the merge host!");
1765            return;
1766        }
1767
1768        ImsCall.Listener listener;
1769        boolean swapRequired = false;
1770
1771        ImsCall finalHostCall;
1772        ImsCall finalPeerCall;
1773
1774        synchronized(ImsCall.this) {
1775            if (isMultiparty()) {
1776                setIsMerged(false);
1777                // if case handles Case 4 explained in callSessionMergeComplete
1778                // otherwise it is case 5
1779                if (!mMergeRequestedByConference) {
1780                    // single call in fg, conference call in bg.
1781                    // Finally conf call becomes active after conference
1782                    this.mHold = false;
1783                    swapRequired = true;
1784                }
1785                maybeMarkPeerAsMerged();
1786                finalHostCall = this;
1787                finalPeerCall = mMergePeer;
1788            } else {
1789                // If we are here, we are not trying to merge a new call into an existing
1790                // conference.  That means that there is a transient session on the merge
1791                // host that represents the future conference once all the parties
1792                // have been added to it.  So make sure that it exists or else something
1793                // very wrong is going on.
1794                if (mTransientConferenceSession == null) {
1795                    loge("processMergeComplete :: No transient session!");
1796                    return;
1797                }
1798                if (mMergePeer == null) {
1799                    loge("processMergeComplete :: No merge peer!");
1800                    return;
1801                }
1802
1803                // Since we are the host, we have the transient session attached to us. Let's detach
1804                // it and figure out where we need to set it for the final conference configuration.
1805                ImsCallSession transientConferenceSession = mTransientConferenceSession;
1806                mTransientConferenceSession = null;
1807
1808                // Clear the listener for this transient session, we'll create a new listener
1809                // when it is attached to the final ImsCall that it should live on.
1810                transientConferenceSession.setListener(null);
1811
1812                // Determine which call the transient session should be moved to.  If the current
1813                // call session is still alive and the merge peer's session is not, we have a
1814                // situation where the current call failed to merge into the conference but the
1815                // merge peer did merge in to the conference.  In this type of scenario the current
1816                // call will continue as a single party call, yet the background call will become
1817                // the conference.
1818
1819                // handles Case 3 explained in callSessionMergeComplete
1820                if (isSessionAlive(mSession) && !isSessionAlive(mMergePeer.getCallSession())) {
1821                    // I'm the host but we are moving the transient session to the peer since its
1822                    // session was disconnected and my session is still alive.  This signifies that
1823                    // their session was properly added to the conference but mine was not because
1824                    // it is probably in the held state as opposed to part of the final conference.
1825                    // In this case, we need to set isMerged to false on both calls so the
1826                    // disconnect sound is called when either call disconnects.
1827                    // Note that this case is only valid if this is an initial conference being
1828                    // brought up.
1829                    mMergePeer.mHold = false;
1830                    this.mHold = true;
1831                    finalHostCall = mMergePeer;
1832                    finalPeerCall = this;
1833                    swapRequired = true;
1834                    setIsMerged(false);
1835                    mMergePeer.setIsMerged(false);
1836                    if (CONF_DBG) {
1837                        logi("processMergeComplete :: transient will transfer to merge peer");
1838                    }
1839                } else if (!isSessionAlive(mSession) &&
1840                                isSessionAlive(mMergePeer.getCallSession())) {
1841                    // Handles case 2 explained in callSessionMergeComplete
1842                    // The transient session stays with us and the disconnect sound should be played
1843                    // when the merge peer eventually disconnects since it was not actually added to
1844                    // the conference and is probably sitting in the held state.
1845                    finalHostCall = this;
1846                    finalPeerCall = mMergePeer;
1847                    swapRequired = false;
1848                    setIsMerged(false);
1849                    mMergePeer.setIsMerged(false); // Play the disconnect sound
1850                    if (CONF_DBG) {
1851                        logi("processMergeComplete :: transient will stay with the merge host");
1852                    }
1853                } else {
1854                    // Handles case 1 explained in callSessionMergeComplete
1855                    // The transient session stays with us and the disconnect sound should not be
1856                    // played when we ripple up the disconnect for the merge peer because it was
1857                    // only disconnected to be added to the conference.
1858                    finalHostCall = this;
1859                    finalPeerCall = mMergePeer;
1860                    maybeMarkPeerAsMerged();
1861                    swapRequired = false;
1862                    setIsMerged(false);
1863                    mMergePeer.setIsMerged(true);
1864                    if (CONF_DBG) {
1865                        logi("processMergeComplete :: transient will stay with us (I'm the host).");
1866                    }
1867                }
1868
1869                if (CONF_DBG) {
1870                    logi("processMergeComplete :: call=" + finalHostCall + " is the final host");
1871                }
1872
1873                // Add the transient session to the ImsCall that ended up being the host for the
1874                // conference.
1875                finalHostCall.setTransientSessionAsPrimary(transientConferenceSession);
1876            }
1877
1878            listener = finalHostCall.mListener;
1879
1880            // Clear all the merge related flags.
1881            clearMergeInfo();
1882
1883            // For the final peer...let's bubble up any possible disconnects that we had
1884            // during the merge process
1885            finalPeerCall.notifySessionTerminatedDuringMerge();
1886            // For the final host, let's just bury the disconnects that we my have received
1887            // during the merge process since we are now the host of the conference call.
1888            finalHostCall.clearSessionTerminationFlags();
1889
1890            // Keep track of the fact that merge host is the origin of a conference call in
1891            // progress.  This is important so that we can later determine if a multiparty ImsCall
1892            // is multiparty because it was the origin of a conference call, or because it is a
1893            // member of a conference on another device.
1894            finalHostCall.mIsConferenceHost = true;
1895        }
1896        if (listener != null) {
1897            try {
1898                // finalPeerCall will have the participant that was not merged and
1899                // it will be held state
1900                // if peer was merged successfully, finalPeerCall will be null
1901                listener.onCallMerged(finalHostCall, finalPeerCall, swapRequired);
1902            } catch (Throwable t) {
1903                loge("processMergeComplete :: ", t);
1904            }
1905            if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
1906                try {
1907                    listener.onConferenceParticipantsStateChanged(this, mConferenceParticipants);
1908                } catch (Throwable t) {
1909                    loge("processMergeComplete :: ", t);
1910                }
1911            }
1912        }
1913        return;
1914    }
1915
1916    /**
1917     * Handles the case where the session has ended during a merge by reporting the termination
1918     * reason to listeners.
1919     */
1920    private void notifySessionTerminatedDuringMerge() {
1921        ImsCall.Listener listener;
1922        boolean notifyFailure = false;
1923        ImsReasonInfo notifyFailureReasonInfo = null;
1924
1925        synchronized(ImsCall.this) {
1926            listener = mListener;
1927            if (mSessionEndDuringMerge) {
1928                // Set some local variables that will send out a notification about a
1929                // previously buried termination callback for our primary session now that
1930                // we know that this is not due to the conference call merging successfully.
1931                if (CONF_DBG) {
1932                    logi("notifySessionTerminatedDuringMerge ::reporting terminate during merge");
1933                }
1934                notifyFailure = true;
1935                notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo;
1936            }
1937            clearSessionTerminationFlags();
1938        }
1939
1940        if (listener != null && notifyFailure) {
1941            try {
1942                processCallTerminated(notifyFailureReasonInfo);
1943            } catch (Throwable t) {
1944                loge("notifySessionTerminatedDuringMerge :: ", t);
1945            }
1946        }
1947    }
1948
1949    private void clearSessionTerminationFlags() {
1950        mSessionEndDuringMerge = false;
1951        mSessionEndDuringMergeReasonInfo = null;
1952    }
1953
1954   /**
1955     * We received a callback from ImsCallSession that a merge failed. Clean up all
1956     * internal state to represent this state change.  The calling function is a callback
1957     * and should have been called on the session that was in the foreground
1958     * when merge() was originally called.  It is assumed that this function will be called
1959     * on the merge host.
1960     *
1961     * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
1962     */
1963    private void processMergeFailed(ImsReasonInfo reasonInfo) {
1964        logi("processMergeFailed :: reason=" + reasonInfo);
1965
1966        ImsCall.Listener listener;
1967        synchronized(ImsCall.this) {
1968            // The logic simplifies if we can assume that this function is only called on
1969            // the merge host.
1970            if (!isMergeHost()) {
1971                loge("processMergeFailed :: We are not the merge host!");
1972                return;
1973            }
1974
1975            // Try to clean up the transient session if it exists.
1976            if (mTransientConferenceSession != null) {
1977                mTransientConferenceSession.setListener(null);
1978                mTransientConferenceSession = null;
1979            }
1980
1981            listener = mListener;
1982
1983            // Ensure the calls being conferenced into the conference has isMerged = false.
1984            // Ensure any terminations are surfaced from this session.
1985            setIsMerged(false);
1986            notifySessionTerminatedDuringMerge();
1987
1988            if (mMergePeer != null) {
1989                // Perform the same cleanup on the merge peer if it exists.
1990                mMergePeer.setIsMerged(false);
1991                mMergePeer.notifySessionTerminatedDuringMerge();
1992            } else {
1993                loge("processMergeFailed :: No merge peer!");
1994            }
1995
1996            // Clear all the various flags around coordinating this merge.
1997            clearMergeInfo();
1998        }
1999        if (listener != null) {
2000            try {
2001                listener.onCallMergeFailed(ImsCall.this, reasonInfo);
2002            } catch (Throwable t) {
2003                loge("processMergeFailed :: ", t);
2004            }
2005        }
2006
2007        return;
2008    }
2009
2010    private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
2011        @Override
2012        public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
2013            logi("callSessionProgressing :: session=" + session + " profile=" + profile);
2014
2015            if (isTransientConferenceSession(session)) {
2016                // If it is a transient (conference) session, there is no action for this signal.
2017                logi("callSessionProgressing :: not supported for transient conference session=" +
2018                        session);
2019                return;
2020            }
2021
2022            ImsCall.Listener listener;
2023
2024            synchronized(ImsCall.this) {
2025                listener = mListener;
2026                mCallProfile.mMediaProfile.copyFrom(profile);
2027            }
2028
2029            if (listener != null) {
2030                try {
2031                    listener.onCallProgressing(ImsCall.this);
2032                } catch (Throwable t) {
2033                    loge("callSessionProgressing :: ", t);
2034                }
2035            }
2036        }
2037
2038        @Override
2039        public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
2040            logi("callSessionStarted :: session=" + session + " profile=" + profile);
2041
2042            if (!isTransientConferenceSession(session)) {
2043                // In the case that we are in the middle of a merge (either host or peer), we have
2044                // closure as far as this call's primary session is concerned.  If we are not
2045                // merging...its a NOOP.
2046                setCallSessionMergePending(false);
2047            } else {
2048                logi("callSessionStarted :: on transient session=" + session);
2049                return;
2050            }
2051
2052            if (isTransientConferenceSession(session)) {
2053                // No further processing is needed if this is the transient session.
2054                return;
2055            }
2056
2057            ImsCall.Listener listener;
2058
2059            synchronized(ImsCall.this) {
2060                listener = mListener;
2061                mCallProfile = profile;
2062            }
2063
2064            if (listener != null) {
2065                try {
2066                    listener.onCallStarted(ImsCall.this);
2067                } catch (Throwable t) {
2068                    loge("callSessionStarted :: ", t);
2069                }
2070            }
2071        }
2072
2073        @Override
2074        public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2075            loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2076
2077            if (isTransientConferenceSession(session)) {
2078                // We should not get this callback for a transient session.
2079                logi("callSessionStartFailed :: not supported for transient conference session=" +
2080                        session);
2081                return;
2082            }
2083
2084            ImsCall.Listener listener;
2085
2086            synchronized(ImsCall.this) {
2087                listener = mListener;
2088                mLastReasonInfo = reasonInfo;
2089            }
2090
2091            if (listener != null) {
2092                try {
2093                    listener.onCallStartFailed(ImsCall.this, reasonInfo);
2094                } catch (Throwable t) {
2095                    loge("callSessionStarted :: ", t);
2096                }
2097            }
2098        }
2099
2100        @Override
2101        public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
2102            logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo);
2103
2104            if (isTransientConferenceSession(session)) {
2105                logi("callSessionTerminated :: on transient session=" + session);
2106                // This is bad, it should be treated much a callSessionMergeFailed since the
2107                // transient session only exists when in the process of a merge and the
2108                // termination of this session is effectively the end of the merge.
2109                processMergeFailed(reasonInfo);
2110                return;
2111            }
2112
2113            // Process the termination first.  If we are in the midst of establishing a conference
2114            // call, we may bury this callback until we are done.  If there so no conference
2115            // call, the code after this function will be a NOOP.
2116            processCallTerminated(reasonInfo);
2117
2118            // If session has terminated, it is no longer pending merge.
2119            setCallSessionMergePending(false);
2120
2121        }
2122
2123        @Override
2124        public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
2125            logi("callSessionHeld :: session=" + session + "profile=" + profile);
2126            ImsCall.Listener listener;
2127
2128            synchronized(ImsCall.this) {
2129                // If the session was held, it is no longer pending a merge -- this means it could
2130                // not be merged into the conference and was held instead.
2131                setCallSessionMergePending(false);
2132
2133                mCallProfile = profile;
2134
2135                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2136                    // This hold request was made to set the stage for a merge.
2137                    mergeInternal();
2138                    return;
2139                }
2140
2141                mUpdateRequest = UPDATE_NONE;
2142                listener = mListener;
2143            }
2144
2145            if (listener != null) {
2146                try {
2147                    listener.onCallHeld(ImsCall.this);
2148                } catch (Throwable t) {
2149                    loge("callSessionHeld :: ", t);
2150                }
2151            }
2152        }
2153
2154        @Override
2155        public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2156            loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo);
2157
2158            if (isTransientConferenceSession(session)) {
2159                // We should not get this callback for a transient session.
2160                logi("callSessionHoldFailed :: not supported for transient conference session=" +
2161                        session);
2162                return;
2163            }
2164
2165            synchronized(mLockObj) {
2166                mHold = false;
2167            }
2168
2169            boolean isHoldForMerge = false;
2170            ImsCall.Listener listener;
2171
2172            synchronized(ImsCall.this) {
2173                if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2174                    isHoldForMerge = true;
2175                }
2176
2177                mUpdateRequest = UPDATE_NONE;
2178                listener = mListener;
2179            }
2180
2181            if (listener != null) {
2182                try {
2183                    listener.onCallHoldFailed(ImsCall.this, reasonInfo);
2184                } catch (Throwable t) {
2185                    loge("callSessionHoldFailed :: ", t);
2186                }
2187            }
2188        }
2189
2190        @Override
2191        public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
2192            logi("callSessionHoldReceived :: session=" + session + "profile=" + profile);
2193
2194            if (isTransientConferenceSession(session)) {
2195                // We should not get this callback for a transient session.
2196                logi("callSessionHoldReceived :: not supported for transient conference session=" +
2197                        session);
2198                return;
2199            }
2200
2201            ImsCall.Listener listener;
2202
2203            synchronized(ImsCall.this) {
2204                listener = mListener;
2205                mCallProfile = profile;
2206            }
2207
2208            if (listener != null) {
2209                try {
2210                    listener.onCallHoldReceived(ImsCall.this);
2211                } catch (Throwable t) {
2212                    loge("callSessionHoldReceived :: ", t);
2213                }
2214            }
2215        }
2216
2217        @Override
2218        public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
2219            logi("callSessionResumed :: session=" + session + "profile=" + profile);
2220
2221            if (isTransientConferenceSession(session)) {
2222                logi("callSessionResumed :: not supported for transient conference session=" +
2223                        session);
2224                return;
2225            }
2226
2227            // If this call was pending a merge, it is not anymore. This is the case when we
2228            // are merging in a new call into an existing conference.
2229            setCallSessionMergePending(false);
2230
2231            // TOOD: When we are merging a new call into an existing conference we are waiting
2232            // for 2 triggers to let us know that the conference has been established, the first
2233            // is a termination for the new calls (since it is added to the conference) the second
2234            // would be a resume on the existing conference.  If the resume comes first, then
2235            // we will make the onCallResumed() callback and its unclear how this will behave if
2236            // the termination has not come yet.
2237
2238            ImsCall.Listener listener;
2239            synchronized(ImsCall.this) {
2240                listener = mListener;
2241                mCallProfile = profile;
2242                mUpdateRequest = UPDATE_NONE;
2243                mHold = false;
2244            }
2245
2246            if (listener != null) {
2247                try {
2248                    listener.onCallResumed(ImsCall.this);
2249                } catch (Throwable t) {
2250                    loge("callSessionResumed :: ", t);
2251                }
2252            }
2253        }
2254
2255        @Override
2256        public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2257            loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2258
2259            if (isTransientConferenceSession(session)) {
2260                logi("callSessionResumeFailed :: not supported for transient conference session=" +
2261                        session);
2262                return;
2263            }
2264
2265            synchronized(mLockObj) {
2266                mHold = true;
2267            }
2268
2269            ImsCall.Listener listener;
2270
2271            synchronized(ImsCall.this) {
2272                listener = mListener;
2273                mUpdateRequest = UPDATE_NONE;
2274            }
2275
2276            if (listener != null) {
2277                try {
2278                    listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2279                } catch (Throwable t) {
2280                    loge("callSessionResumeFailed :: ", t);
2281                }
2282            }
2283        }
2284
2285        @Override
2286        public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
2287            logi("callSessionResumeReceived :: session=" + session + "profile=" + profile);
2288
2289            if (isTransientConferenceSession(session)) {
2290                logi("callSessionResumeReceived :: not supported for transient conference session=" +
2291                        session);
2292                return;
2293            }
2294
2295            ImsCall.Listener listener;
2296
2297            synchronized(ImsCall.this) {
2298                listener = mListener;
2299                mCallProfile = profile;
2300            }
2301
2302            if (listener != null) {
2303                try {
2304                    listener.onCallResumeReceived(ImsCall.this);
2305                } catch (Throwable t) {
2306                    loge("callSessionResumeReceived :: ", t);
2307                }
2308            }
2309        }
2310
2311        @Override
2312        public void callSessionMergeStarted(ImsCallSession session,
2313                ImsCallSession newSession, ImsCallProfile profile) {
2314            logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession +
2315                    ", profile=" + profile);
2316
2317            if (!isCallSessionMergePending()) {
2318                // Odd, we are not in the midst of merging anything.
2319                logi("callSessionMergeStarted :: no merge in progress.");
2320                return;
2321            }
2322
2323            // There are 2 ways that we can go here.  If the session that supplied the params
2324            // is not null, then it is the new session that represents the new conference
2325            // if the merge succeeds. If it is null, the merge is happening on our current
2326            // ImsCallSession.
2327            if (session == null) {
2328                // Everything is already set up and we just need to make sure
2329                // that we properly respond to all the future callbacks about
2330                // this merge.
2331                if (CONF_DBG) {
2332                    logi("callSessionMergeStarted :: merging into existing ImsCallSession");
2333                }
2334                return;
2335            }
2336
2337            if (CONF_DBG) {
2338                logi("callSessionMergeStarted ::  setting our transient ImsCallSession");
2339            }
2340
2341            // If we are here, this means that we are creating a new conference and
2342            // we need to do some extra work around managing a new ImsCallSession that
2343            // could represent our new ImsCallSession if the merge succeeds.
2344            synchronized(ImsCall.this) {
2345                // Keep track of this session for future callbacks to indicate success
2346                // or failure of this merge.
2347                mTransientConferenceSession = newSession;
2348                mTransientConferenceSession.setListener(createCallSessionListener());
2349            }
2350
2351            return;
2352        }
2353
2354        /*
2355         * This method check if session exists as a session on the current
2356         * ImsCall or its counterpart if it is in the process of a conference
2357         */
2358        private boolean doesCallSessionExistsInMerge(ImsCallSession cs) {
2359            String callId = cs.getCallId();
2360            return ((isMergeHost() && Objects.equals(mMergePeer.mSession.getCallId(), callId)) ||
2361                    (isMergePeer() && Objects.equals(mMergeHost.mSession.getCallId(), callId)) ||
2362                    Objects.equals(mSession.getCallId(), callId));
2363        }
2364
2365        /**
2366         * We received a callback from ImsCallSession that merge completed.
2367         * @param session - this session can have 2 values based on the below scenarios
2368         *
2369	 * Conference Scenarios :
2370         * Case 1 - 3 way success case
2371         * Case 2 - 3 way success case but held call fails to merge
2372         * Case 3 - 3 way success case but active call fails to merge
2373         * case 4 - 4 way success case, where merge is initiated on the foreground single-party
2374         *          call and the conference (mergeHost) is the background call.
2375         * case 5 - 4 way success case, where merge is initiated on the foreground conference
2376         *          call (mergeHost) and the single party call is in the background.
2377         *
2378         * Conference Result:
2379         * session : active session after conference
2380         * session = new session for case 1, 2, 3. Should be considered as mTransientConferencession
2381         * session = Active conference session for case 5, same as current session,
2382         *           mergehost was foreground call
2383         *           mTransientConferencession will be null
2384         * session = Active conference session for case 4, mergeHost was background call
2385         *           mTransientConferencession will be null
2386         */
2387        @Override
2388        public void callSessionMergeComplete(ImsCallSession session) {
2389            logi("callSessionMergeComplete :: session=" + session);
2390            if (!isMergeHost()) {
2391                // Handles case 4
2392                mMergeHost.processMergeComplete();
2393            } else {
2394                // Handles case 1, 2, 3, 5
2395                mTransientConferenceSession = doesCallSessionExistsInMerge(session) ?
2396                    null: session;
2397                processMergeComplete();
2398            }
2399        }
2400
2401        @Override
2402        public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2403            loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2404
2405            // Its possible that there could be threading issues with the other thread handling
2406            // the other call. This could affect our state.
2407            synchronized (ImsCall.this) {
2408                // Let's tell our parent ImsCall that the merge has failed and we need to clean
2409                // up any temporary, transient state.  Note this only gets called for an initial
2410                // conference.  If a merge into an existing conference fails, the two sessions will
2411                // just go back to their original state (ACTIVE or HELD).
2412                if (isMergeHost()) {
2413                    processMergeFailed(reasonInfo);
2414                } else if (mMergeHost != null) {
2415                    mMergeHost.processMergeFailed(reasonInfo);
2416                } else {
2417                    loge("callSessionMergeFailed :: No merge host for this conference!");
2418                }
2419            }
2420        }
2421
2422        @Override
2423        public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2424            logi("callSessionUpdated :: session=" + session + " profile=" + profile);
2425
2426            if (isTransientConferenceSession(session)) {
2427                logi("callSessionUpdated :: not supported for transient conference session=" +
2428                        session);
2429                return;
2430            }
2431
2432            ImsCall.Listener listener;
2433
2434            synchronized(ImsCall.this) {
2435                listener = mListener;
2436                mCallProfile = profile;
2437            }
2438
2439            if (listener != null) {
2440                try {
2441                    listener.onCallUpdated(ImsCall.this);
2442                } catch (Throwable t) {
2443                    loge("callSessionUpdated :: ", t);
2444                }
2445            }
2446        }
2447
2448        @Override
2449        public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2450            loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2451
2452            if (isTransientConferenceSession(session)) {
2453                logi("callSessionUpdateFailed :: not supported for transient conference session=" +
2454                        session);
2455                return;
2456            }
2457
2458            ImsCall.Listener listener;
2459
2460            synchronized(ImsCall.this) {
2461                listener = mListener;
2462                mUpdateRequest = UPDATE_NONE;
2463            }
2464
2465            if (listener != null) {
2466                try {
2467                    listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2468                } catch (Throwable t) {
2469                    loge("callSessionUpdateFailed :: ", t);
2470                }
2471            }
2472        }
2473
2474        @Override
2475        public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2476            logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile);
2477
2478            if (isTransientConferenceSession(session)) {
2479                logi("callSessionUpdateReceived :: not supported for transient conference " +
2480                        "session=" + session);
2481                return;
2482            }
2483
2484            ImsCall.Listener listener;
2485
2486            synchronized(ImsCall.this) {
2487                listener = mListener;
2488                mProposedCallProfile = profile;
2489                mUpdateRequest = UPDATE_UNSPECIFIED;
2490            }
2491
2492            if (listener != null) {
2493                try {
2494                    listener.onCallUpdateReceived(ImsCall.this);
2495                } catch (Throwable t) {
2496                    loge("callSessionUpdateReceived :: ", t);
2497                }
2498            }
2499        }
2500
2501        @Override
2502        public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2503                ImsCallProfile profile) {
2504            logi("callSessionConferenceExtended :: session=" + session  + " newSession=" +
2505                    newSession + ", profile=" + profile);
2506
2507            if (isTransientConferenceSession(session)) {
2508                logi("callSessionConferenceExtended :: not supported for transient conference " +
2509                        "session=" + session);
2510                return;
2511            }
2512
2513            ImsCall newCall = createNewCall(newSession, profile);
2514
2515            if (newCall == null) {
2516                callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2517                return;
2518            }
2519
2520            ImsCall.Listener listener;
2521
2522            synchronized(ImsCall.this) {
2523                listener = mListener;
2524                mUpdateRequest = UPDATE_NONE;
2525            }
2526
2527            if (listener != null) {
2528                try {
2529                    listener.onCallConferenceExtended(ImsCall.this, newCall);
2530                } catch (Throwable t) {
2531                    loge("callSessionConferenceExtended :: ", t);
2532                }
2533            }
2534        }
2535
2536        @Override
2537        public void callSessionConferenceExtendFailed(ImsCallSession session,
2538                ImsReasonInfo reasonInfo) {
2539            loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo);
2540
2541            if (isTransientConferenceSession(session)) {
2542                logi("callSessionConferenceExtendFailed :: not supported for transient " +
2543                        "conference session=" + session);
2544                return;
2545            }
2546
2547            ImsCall.Listener listener;
2548
2549            synchronized(ImsCall.this) {
2550                listener = mListener;
2551                mUpdateRequest = UPDATE_NONE;
2552            }
2553
2554            if (listener != null) {
2555                try {
2556                    listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2557                } catch (Throwable t) {
2558                    loge("callSessionConferenceExtendFailed :: ", t);
2559                }
2560            }
2561        }
2562
2563        @Override
2564        public void callSessionConferenceExtendReceived(ImsCallSession session,
2565                ImsCallSession newSession, ImsCallProfile profile) {
2566            logi("callSessionConferenceExtendReceived :: newSession=" + newSession +
2567                    ", profile=" + profile);
2568
2569            if (isTransientConferenceSession(session)) {
2570                logi("callSessionConferenceExtendReceived :: not supported for transient " +
2571                        "conference session" + session);
2572                return;
2573            }
2574
2575            ImsCall newCall = createNewCall(newSession, profile);
2576
2577            if (newCall == null) {
2578                // Should all the calls be terminated...???
2579                return;
2580            }
2581
2582            ImsCall.Listener listener;
2583
2584            synchronized(ImsCall.this) {
2585                listener = mListener;
2586            }
2587
2588            if (listener != null) {
2589                try {
2590                    listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2591                } catch (Throwable t) {
2592                    loge("callSessionConferenceExtendReceived :: ", t);
2593                }
2594            }
2595        }
2596
2597        @Override
2598        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2599            logi("callSessionInviteParticipantsRequestDelivered ::");
2600
2601            if (isTransientConferenceSession(session)) {
2602                logi("callSessionInviteParticipantsRequestDelivered :: not supported for " +
2603                        "conference session=" + session);
2604                return;
2605            }
2606
2607            ImsCall.Listener listener;
2608
2609            synchronized(ImsCall.this) {
2610                listener = mListener;
2611            }
2612
2613            if (listener != null) {
2614                try {
2615                    listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2616                } catch (Throwable t) {
2617                    loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2618                }
2619            }
2620        }
2621
2622        @Override
2623        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2624                ImsReasonInfo reasonInfo) {
2625            loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2626
2627            if (isTransientConferenceSession(session)) {
2628                logi("callSessionInviteParticipantsRequestFailed :: not supported for " +
2629                        "conference session=" + session);
2630                return;
2631            }
2632
2633            ImsCall.Listener listener;
2634
2635            synchronized(ImsCall.this) {
2636                listener = mListener;
2637            }
2638
2639            if (listener != null) {
2640                try {
2641                    listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2642                } catch (Throwable t) {
2643                    loge("callSessionInviteParticipantsRequestFailed :: ", t);
2644                }
2645            }
2646        }
2647
2648        @Override
2649        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2650            logi("callSessionRemoveParticipantsRequestDelivered ::");
2651
2652            if (isTransientConferenceSession(session)) {
2653                logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
2654                        "conference session=" + session);
2655                return;
2656            }
2657
2658            ImsCall.Listener listener;
2659
2660            synchronized(ImsCall.this) {
2661                listener = mListener;
2662            }
2663
2664            if (listener != null) {
2665                try {
2666                    listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2667                } catch (Throwable t) {
2668                    loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2669                }
2670            }
2671        }
2672
2673        @Override
2674        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2675                ImsReasonInfo reasonInfo) {
2676            loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2677
2678            if (isTransientConferenceSession(session)) {
2679                logi("callSessionRemoveParticipantsRequestFailed :: not supported for " +
2680                        "conference session=" + session);
2681                return;
2682            }
2683
2684            ImsCall.Listener listener;
2685
2686            synchronized(ImsCall.this) {
2687                listener = mListener;
2688            }
2689
2690            if (listener != null) {
2691                try {
2692                    listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2693                } catch (Throwable t) {
2694                    loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2695                }
2696            }
2697        }
2698
2699        @Override
2700        public void callSessionConferenceStateUpdated(ImsCallSession session,
2701                ImsConferenceState state) {
2702            logi("callSessionConferenceStateUpdated :: state=" + state);
2703
2704            conferenceStateUpdated(state);
2705        }
2706
2707        @Override
2708        public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2709                String ussdMessage) {
2710            logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2711                    ussdMessage);
2712
2713            if (isTransientConferenceSession(session)) {
2714                logi("callSessionUssdMessageReceived :: not supported for transient " +
2715                        "conference session=" + session);
2716                return;
2717            }
2718
2719            ImsCall.Listener listener;
2720
2721            synchronized(ImsCall.this) {
2722                listener = mListener;
2723            }
2724
2725            if (listener != null) {
2726                try {
2727                    listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2728                } catch (Throwable t) {
2729                    loge("callSessionUssdMessageReceived :: ", t);
2730                }
2731            }
2732        }
2733
2734        @Override
2735        public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
2736            logi("callSessionTtyModeReceived :: mode=" + mode);
2737
2738            ImsCall.Listener listener;
2739
2740            synchronized(ImsCall.this) {
2741                listener = mListener;
2742            }
2743
2744            if (listener != null) {
2745                try {
2746                    listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
2747                } catch (Throwable t) {
2748                    loge("callSessionTtyModeReceived :: ", t);
2749                }
2750            }
2751        }
2752
2753        /**
2754         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
2755         *
2756         * @param session The call session.
2757         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
2758         *      otherwise.
2759         */
2760        @Override
2761        public void callSessionMultipartyStateChanged(ImsCallSession session,
2762                boolean isMultiParty) {
2763            if (VDBG) {
2764                logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
2765                        : "N"));
2766            }
2767
2768            ImsCall.Listener listener;
2769
2770            synchronized(ImsCall.this) {
2771                listener = mListener;
2772            }
2773
2774            if (listener != null) {
2775                try {
2776                    listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
2777                } catch (Throwable t) {
2778                    loge("callSessionMultipartyStateChanged :: ", t);
2779                }
2780            }
2781        }
2782
2783        public void callSessionHandover(ImsCallSession session, int srcAccessTech,
2784            int targetAccessTech, ImsReasonInfo reasonInfo) {
2785            logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
2786                srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
2787                reasonInfo);
2788
2789            ImsCall.Listener listener;
2790
2791            synchronized(ImsCall.this) {
2792                listener = mListener;
2793            }
2794
2795            if (listener != null) {
2796                try {
2797                    listener.onCallHandover(ImsCall.this, srcAccessTech, targetAccessTech,
2798                        reasonInfo);
2799                } catch (Throwable t) {
2800                    loge("callSessionHandover :: ", t);
2801                }
2802            }
2803        }
2804
2805        @Override
2806        public void callSessionHandoverFailed(ImsCallSession session, int srcAccessTech,
2807            int targetAccessTech, ImsReasonInfo reasonInfo) {
2808            loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" +
2809                srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
2810                reasonInfo);
2811
2812            ImsCall.Listener listener;
2813
2814            synchronized(ImsCall.this) {
2815                listener = mListener;
2816            }
2817
2818            if (listener != null) {
2819                try {
2820                    listener.onCallHandoverFailed(ImsCall.this, srcAccessTech, targetAccessTech,
2821                        reasonInfo);
2822                } catch (Throwable t) {
2823                    loge("callSessionHandoverFailed :: ", t);
2824                }
2825            }
2826        }
2827    }
2828
2829    /**
2830     * Report a new conference state to the current {@link ImsCall} and inform listeners of the
2831     * change.  Marked as {@code VisibleForTesting} so that the
2832     * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
2833     * event package into a regular ongoing IMS call.
2834     *
2835     * @param state The {@link ImsConferenceState}.
2836     */
2837    @VisibleForTesting
2838    public void conferenceStateUpdated(ImsConferenceState state) {
2839        Listener listener;
2840
2841        synchronized(this) {
2842            notifyConferenceStateUpdated(state);
2843            listener = mListener;
2844        }
2845
2846        if (listener != null) {
2847            try {
2848                listener.onCallConferenceStateUpdated(this, state);
2849            } catch (Throwable t) {
2850                loge("callSessionConferenceStateUpdated :: ", t);
2851            }
2852        }
2853    }
2854
2855    /**
2856     * Provides a human-readable string representation of an update request.
2857     *
2858     * @param updateRequest The update request.
2859     * @return The string representation.
2860     */
2861    private String updateRequestToString(int updateRequest) {
2862        switch (updateRequest) {
2863            case UPDATE_NONE:
2864                return "NONE";
2865            case UPDATE_HOLD:
2866                return "HOLD";
2867            case UPDATE_HOLD_MERGE:
2868                return "HOLD_MERGE";
2869            case UPDATE_RESUME:
2870                return "RESUME";
2871            case UPDATE_MERGE:
2872                return "MERGE";
2873            case UPDATE_EXTEND_TO_CONFERENCE:
2874                return "EXTEND_TO_CONFERENCE";
2875            case UPDATE_UNSPECIFIED:
2876                return "UNSPECIFIED";
2877            default:
2878                return "UNKNOWN";
2879        }
2880    }
2881
2882    /**
2883     * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
2884     * severed at the same time.
2885     */
2886    private void clearMergeInfo() {
2887        if (CONF_DBG) {
2888            logi("clearMergeInfo :: clearing all merge info");
2889        }
2890
2891        // First clear out the merge partner then clear ourselves out.
2892        if (mMergeHost != null) {
2893            mMergeHost.mMergePeer = null;
2894            mMergeHost.mUpdateRequest = UPDATE_NONE;
2895            mMergeHost.mCallSessionMergePending = false;
2896        }
2897        if (mMergePeer != null) {
2898            mMergePeer.mMergeHost = null;
2899            mMergePeer.mUpdateRequest = UPDATE_NONE;
2900            mMergePeer.mCallSessionMergePending = false;
2901        }
2902        mMergeHost = null;
2903        mMergePeer = null;
2904        mUpdateRequest = UPDATE_NONE;
2905        mCallSessionMergePending = false;
2906    }
2907
2908    /**
2909     * Sets the merge peer for the current call.  The merge peer is the background call that will be
2910     * merged into this call.  On the merge peer, sets the merge host to be this call.
2911     *
2912     * @param mergePeer The peer call to be merged into this one.
2913     */
2914    private void setMergePeer(ImsCall mergePeer) {
2915        mMergePeer = mergePeer;
2916        mMergeHost = null;
2917
2918        mergePeer.mMergeHost = ImsCall.this;
2919        mergePeer.mMergePeer = null;
2920    }
2921
2922    /**
2923     * Sets the merge hody for the current call.  The merge host is the foreground call this call
2924     * will be merged into.  On the merge host, sets the merge peer to be this call.
2925     *
2926     * @param mergeHost The merge host this call will be merged into.
2927     */
2928    public void setMergeHost(ImsCall mergeHost) {
2929        mMergeHost = mergeHost;
2930        mMergePeer = null;
2931
2932        mergeHost.mMergeHost = null;
2933        mergeHost.mMergePeer = ImsCall.this;
2934    }
2935
2936    /**
2937     * Determines if the current call is in the process of merging with another call or conference.
2938     *
2939     * @return {@code true} if in the process of merging.
2940     */
2941    private boolean isMerging() {
2942        return mMergePeer != null || mMergeHost != null;
2943    }
2944
2945    /**
2946     * Determines if the current call is the host of the merge.
2947     *
2948     * @return {@code true} if the call is the merge host.
2949     */
2950    private boolean isMergeHost() {
2951        return mMergePeer != null && mMergeHost == null;
2952    }
2953
2954    /**
2955     * Determines if the current call is the peer of the merge.
2956     *
2957     * @return {@code true} if the call is the merge peer.
2958     */
2959    private boolean isMergePeer() {
2960        return mMergePeer == null && mMergeHost != null;
2961    }
2962
2963    /**
2964     * Determines if the call session is pending merge into a conference or not.
2965     *
2966     * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
2967     */
2968    private boolean isCallSessionMergePending() {
2969        return mCallSessionMergePending;
2970    }
2971
2972    /**
2973     * Sets flag indicating whether the call session is pending merge into a conference or not.
2974     *
2975     * @param callSessionMergePending {@code true} if a merge into the conference is pending,
2976     *      {@code false} otherwise.
2977     */
2978    private void setCallSessionMergePending(boolean callSessionMergePending) {
2979        mCallSessionMergePending = callSessionMergePending;
2980    }
2981
2982    /**
2983     * Determines if there is a conference merge in process.  If there is a merge in process,
2984     * determines if both the merge host and peer sessions have completed the merge process.  This
2985     * means that we have received terminate or hold signals for the sessions, indicating that they
2986     * are no longer in the process of being merged into the conference.
2987     * <p>
2988     * The sessions are considered to have merged if: both calls still have merge peer/host
2989     * relationships configured,  both sessions are not waiting to be merged into the conference,
2990     * and the transient conference session is alive in the case of an initial conference.
2991     *
2992     * @return {@code true} where the host and peer sessions have finished merging into the
2993     *      conference, {@code false} if the merge has not yet completed, and {@code false} if there
2994     *      is no conference merge in progress.
2995     */
2996    private boolean shouldProcessConferenceResult() {
2997        boolean areMergeTriggersDone = false;
2998
2999        synchronized (ImsCall.this) {
3000            // if there is a merge going on, then the merge host/peer relationships should have been
3001            // set up.  This works for both the initial conference or merging a call into an
3002            // existing conference.
3003            if (!isMergeHost() && !isMergePeer()) {
3004                if (CONF_DBG) {
3005                    loge("shouldProcessConferenceResult :: no merge in progress");
3006                }
3007                return false;
3008            }
3009
3010            // There is a merge in progress, so check the sessions to ensure:
3011            // 1. Both calls have completed being merged (or failing to merge) into the conference.
3012            // 2. The transient conference session is alive.
3013            if (isMergeHost()) {
3014                if (CONF_DBG) {
3015                    logi("shouldProcessConferenceResult :: We are a merge host");
3016                    logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer);
3017                }
3018                areMergeTriggersDone = !isCallSessionMergePending() &&
3019                        !mMergePeer.isCallSessionMergePending();
3020                if (!isMultiparty()) {
3021                    // Only check the transient session when there is no existing conference
3022                    areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession);
3023                }
3024            } else if (isMergePeer()) {
3025                if (CONF_DBG) {
3026                    logi("shouldProcessConferenceResult :: We are a merge peer");
3027                    logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost);
3028                }
3029                areMergeTriggersDone = !isCallSessionMergePending() &&
3030                        !mMergeHost.isCallSessionMergePending();
3031                if (!mMergeHost.isMultiparty()) {
3032                    // Only check the transient session when there is no existing conference
3033                    areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession);
3034                } else {
3035                    // This else block is a special case for Verizon to handle these steps
3036                    // 1. Establish a conference call.
3037                    // 2. Add a new call (conference in in BG)
3038                    // 3. Swap (conference active on FG)
3039                    // 4. Merge
3040                    // What happens here is that the BG call gets a terminated callback
3041                    // because it was added to the conference. I've seen where
3042                    // the FG gets no callback at all because its already active.
3043                    // So if we continue to wait for it to set its isCallSessionMerging
3044                    // flag to false...we'll be waiting forever.
3045                    areMergeTriggersDone = !isCallSessionMergePending();
3046                }
3047            } else {
3048                // Realistically this shouldn't happen, but best to be safe.
3049                loge("shouldProcessConferenceResult : merge in progress but call is neither" +
3050                        " host nor peer.");
3051            }
3052            if (CONF_DBG) {
3053                logi("shouldProcessConferenceResult :: returning:" +
3054                        (areMergeTriggersDone ? "true" : "false"));
3055            }
3056        }
3057        return areMergeTriggersDone;
3058    }
3059
3060    /**
3061     * Provides a string representation of the {@link ImsCall}.  Primarily intended for use in log
3062     * statements.
3063     *
3064     * @return String representation of call.
3065     */
3066    @Override
3067    public String toString() {
3068        StringBuilder sb = new StringBuilder();
3069        sb.append("[ImsCall objId:");
3070        sb.append(System.identityHashCode(this));
3071        sb.append(" onHold:");
3072        sb.append(isOnHold() ? "Y" : "N");
3073        sb.append(" mute:");
3074        sb.append(isMuted() ? "Y" : "N");
3075        sb.append(" updateRequest:");
3076        sb.append(updateRequestToString(mUpdateRequest));
3077        sb.append(" merging:");
3078        sb.append(isMerging() ? "Y" : "N");
3079        if (isMerging()) {
3080            if (isMergePeer()) {
3081                sb.append("P");
3082            } else {
3083                sb.append("H");
3084            }
3085        }
3086        sb.append(" merge action pending:");
3087        sb.append(isCallSessionMergePending() ? "Y" : "N");
3088        sb.append(" merged:");
3089        sb.append(isMerged() ? "Y" : "N");
3090        sb.append(" multiParty:");
3091        sb.append(isMultiparty() ? "Y" : "N");
3092        sb.append(" confHost:");
3093        sb.append(isConferenceHost() ? "Y" : "N");
3094        sb.append(" buried term:");
3095        sb.append(mSessionEndDuringMerge ? "Y" : "N");
3096        sb.append(" session:");
3097        sb.append(mSession);
3098        sb.append(" transientSession:");
3099        sb.append(mTransientConferenceSession);
3100        sb.append("]");
3101        return sb.toString();
3102    }
3103
3104    private void throwImsException(Throwable t, int code) throws ImsException {
3105        if (t instanceof ImsException) {
3106            throw (ImsException) t;
3107        } else {
3108            throw new ImsException(String.valueOf(code), t, code);
3109        }
3110    }
3111
3112    /**
3113     * Append the ImsCall information to the provided string. Usefull for as a logging helper.
3114     * @param s The original string
3115     * @return The original string with {@code ImsCall} information appended to it.
3116     */
3117    private String appendImsCallInfoToString(String s) {
3118        StringBuilder sb = new StringBuilder();
3119        sb.append(s);
3120        sb.append(" ImsCall=");
3121        sb.append(ImsCall.this);
3122        return sb.toString();
3123    }
3124
3125    /**
3126     * Log a string to the radio buffer at the info level.
3127     * @param s The message to log
3128     */
3129    private void logi(String s) {
3130        Log.i(TAG, appendImsCallInfoToString(s));
3131    }
3132
3133    /**
3134     * Log a string to the radio buffer at the debug level.
3135     * @param s The message to log
3136     */
3137    private void logd(String s) {
3138        Log.d(TAG, appendImsCallInfoToString(s));
3139    }
3140
3141    /**
3142     * Log a string to the radio buffer at the verbose level.
3143     * @param s The message to log
3144     */
3145    private void logv(String s) {
3146        Log.v(TAG, appendImsCallInfoToString(s));
3147    }
3148
3149    /**
3150     * Log a string to the radio buffer at the error level.
3151     * @param s The message to log
3152     */
3153    private void loge(String s) {
3154        Log.e(TAG, appendImsCallInfoToString(s));
3155    }
3156
3157    /**
3158     * Log a string to the radio buffer at the error level with a throwable
3159     * @param s The message to log
3160     * @param t The associated throwable
3161     */
3162    private void loge(String s, Throwable t) {
3163        Log.e(TAG, appendImsCallInfoToString(s), t);
3164    }
3165
3166}
3167