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