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