ImsCall.java revision d9ed874439293ed6c34ce27a3689711c986f977b
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            return;
2303        }
2304
2305        /*
2306         * This method check if session exists as a session on the current
2307         * ImsCall or its counterpart if it is in the process of a conference
2308         */
2309        private boolean doesCallSessionExistsInMerge(ImsCallSession cs) {
2310            String callId = cs.getCallId();
2311            return ((isMergeHost() && Objects.equals(mMergePeer.mSession.getCallId(), callId)) ||
2312                    (isMergePeer() && Objects.equals(mMergeHost.mSession.getCallId(), callId)) ||
2313                    Objects.equals(mSession.getCallId(), callId));
2314        }
2315
2316        /**
2317         * We received a callback from ImsCallSession that merge completed.
2318         * @param newSession - this session can have 2 values based on the below scenarios
2319         *
2320	 * Conference Scenarios :
2321         * Case 1 - 3 way success case
2322         * Case 2 - 3 way success case but held call fails to merge
2323         * Case 3 - 3 way success case but active call fails to merge
2324         * case 4 - 4 way success case, where merge is initiated on the foreground single-party
2325         *          call and the conference (mergeHost) is the background call.
2326         * case 5 - 4 way success case, where merge is initiated on the foreground conference
2327         *          call (mergeHost) and the single party call is in the background.
2328         *
2329         * Conference Result:
2330         * session : new session after conference
2331         * newSession = new session for case 1, 2, 3.
2332         *              Should be considered as mTransientConferencession
2333         * newSession = Active conference session for case 5 will be null
2334         *              mergehost was foreground call
2335         *              mTransientConferencession will be null
2336         * newSession = Active conference session for case 4 will be null
2337         *              mergeHost was background call
2338         *              mTransientConferencession will be null
2339         */
2340        @Override
2341        public void callSessionMergeComplete(ImsCallSession newSession) {
2342            logi("callSessionMergeComplete :: newSession =" + newSession);
2343            if (!isMergeHost()) {
2344                // Handles case 4
2345                mMergeHost.processMergeComplete();
2346            } else {
2347                // Handles case 1, 2, 3
2348                if (newSession != null) {
2349                    mTransientConferenceSession = doesCallSessionExistsInMerge(newSession) ?
2350                            null: newSession;
2351                }
2352                // Handles case 5
2353                processMergeComplete();
2354            }
2355        }
2356
2357        @Override
2358        public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2359            loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2360
2361            // Its possible that there could be threading issues with the other thread handling
2362            // the other call. This could affect our state.
2363            synchronized (ImsCall.this) {
2364                // Let's tell our parent ImsCall that the merge has failed and we need to clean
2365                // up any temporary, transient state.  Note this only gets called for an initial
2366                // conference.  If a merge into an existing conference fails, the two sessions will
2367                // just go back to their original state (ACTIVE or HELD).
2368                if (isMergeHost()) {
2369                    processMergeFailed(reasonInfo);
2370                } else if (mMergeHost != null) {
2371                    mMergeHost.processMergeFailed(reasonInfo);
2372                } else {
2373                    loge("callSessionMergeFailed :: No merge host for this conference!");
2374                }
2375            }
2376        }
2377
2378        @Override
2379        public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2380            logi("callSessionUpdated :: session=" + session + " profile=" + profile);
2381
2382            if (isTransientConferenceSession(session)) {
2383                logi("callSessionUpdated :: not supported for transient conference session=" +
2384                        session);
2385                return;
2386            }
2387
2388            ImsCall.Listener listener;
2389
2390            synchronized(ImsCall.this) {
2391                listener = mListener;
2392                mCallProfile = profile;
2393            }
2394
2395            if (listener != null) {
2396                try {
2397                    listener.onCallUpdated(ImsCall.this);
2398                } catch (Throwable t) {
2399                    loge("callSessionUpdated :: ", t);
2400                }
2401            }
2402        }
2403
2404        @Override
2405        public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2406            loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2407
2408            if (isTransientConferenceSession(session)) {
2409                logi("callSessionUpdateFailed :: not supported for transient conference session=" +
2410                        session);
2411                return;
2412            }
2413
2414            ImsCall.Listener listener;
2415
2416            synchronized(ImsCall.this) {
2417                listener = mListener;
2418                mUpdateRequest = UPDATE_NONE;
2419            }
2420
2421            if (listener != null) {
2422                try {
2423                    listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2424                } catch (Throwable t) {
2425                    loge("callSessionUpdateFailed :: ", t);
2426                }
2427            }
2428        }
2429
2430        @Override
2431        public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2432            logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile);
2433
2434            if (isTransientConferenceSession(session)) {
2435                logi("callSessionUpdateReceived :: not supported for transient conference " +
2436                        "session=" + session);
2437                return;
2438            }
2439
2440            ImsCall.Listener listener;
2441
2442            synchronized(ImsCall.this) {
2443                listener = mListener;
2444                mProposedCallProfile = profile;
2445                mUpdateRequest = UPDATE_UNSPECIFIED;
2446            }
2447
2448            if (listener != null) {
2449                try {
2450                    listener.onCallUpdateReceived(ImsCall.this);
2451                } catch (Throwable t) {
2452                    loge("callSessionUpdateReceived :: ", t);
2453                }
2454            }
2455        }
2456
2457        @Override
2458        public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2459                ImsCallProfile profile) {
2460            logi("callSessionConferenceExtended :: session=" + session  + " newSession=" +
2461                    newSession + ", profile=" + profile);
2462
2463            if (isTransientConferenceSession(session)) {
2464                logi("callSessionConferenceExtended :: not supported for transient conference " +
2465                        "session=" + session);
2466                return;
2467            }
2468
2469            ImsCall newCall = createNewCall(newSession, profile);
2470
2471            if (newCall == null) {
2472                callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2473                return;
2474            }
2475
2476            ImsCall.Listener listener;
2477
2478            synchronized(ImsCall.this) {
2479                listener = mListener;
2480                mUpdateRequest = UPDATE_NONE;
2481            }
2482
2483            if (listener != null) {
2484                try {
2485                    listener.onCallConferenceExtended(ImsCall.this, newCall);
2486                } catch (Throwable t) {
2487                    loge("callSessionConferenceExtended :: ", t);
2488                }
2489            }
2490        }
2491
2492        @Override
2493        public void callSessionConferenceExtendFailed(ImsCallSession session,
2494                ImsReasonInfo reasonInfo) {
2495            loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo);
2496
2497            if (isTransientConferenceSession(session)) {
2498                logi("callSessionConferenceExtendFailed :: not supported for transient " +
2499                        "conference session=" + session);
2500                return;
2501            }
2502
2503            ImsCall.Listener listener;
2504
2505            synchronized(ImsCall.this) {
2506                listener = mListener;
2507                mUpdateRequest = UPDATE_NONE;
2508            }
2509
2510            if (listener != null) {
2511                try {
2512                    listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2513                } catch (Throwable t) {
2514                    loge("callSessionConferenceExtendFailed :: ", t);
2515                }
2516            }
2517        }
2518
2519        @Override
2520        public void callSessionConferenceExtendReceived(ImsCallSession session,
2521                ImsCallSession newSession, ImsCallProfile profile) {
2522            logi("callSessionConferenceExtendReceived :: newSession=" + newSession +
2523                    ", profile=" + profile);
2524
2525            if (isTransientConferenceSession(session)) {
2526                logi("callSessionConferenceExtendReceived :: not supported for transient " +
2527                        "conference session" + session);
2528                return;
2529            }
2530
2531            ImsCall newCall = createNewCall(newSession, profile);
2532
2533            if (newCall == null) {
2534                // Should all the calls be terminated...???
2535                return;
2536            }
2537
2538            ImsCall.Listener listener;
2539
2540            synchronized(ImsCall.this) {
2541                listener = mListener;
2542            }
2543
2544            if (listener != null) {
2545                try {
2546                    listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2547                } catch (Throwable t) {
2548                    loge("callSessionConferenceExtendReceived :: ", t);
2549                }
2550            }
2551        }
2552
2553        @Override
2554        public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2555            logi("callSessionInviteParticipantsRequestDelivered ::");
2556
2557            if (isTransientConferenceSession(session)) {
2558                logi("callSessionInviteParticipantsRequestDelivered :: not supported for " +
2559                        "conference session=" + session);
2560                return;
2561            }
2562
2563            ImsCall.Listener listener;
2564
2565            synchronized(ImsCall.this) {
2566                listener = mListener;
2567            }
2568
2569            if (listener != null) {
2570                try {
2571                    listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2572                } catch (Throwable t) {
2573                    loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2574                }
2575            }
2576        }
2577
2578        @Override
2579        public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2580                ImsReasonInfo reasonInfo) {
2581            loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2582
2583            if (isTransientConferenceSession(session)) {
2584                logi("callSessionInviteParticipantsRequestFailed :: not supported for " +
2585                        "conference session=" + session);
2586                return;
2587            }
2588
2589            ImsCall.Listener listener;
2590
2591            synchronized(ImsCall.this) {
2592                listener = mListener;
2593            }
2594
2595            if (listener != null) {
2596                try {
2597                    listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2598                } catch (Throwable t) {
2599                    loge("callSessionInviteParticipantsRequestFailed :: ", t);
2600                }
2601            }
2602        }
2603
2604        @Override
2605        public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2606            logi("callSessionRemoveParticipantsRequestDelivered ::");
2607
2608            if (isTransientConferenceSession(session)) {
2609                logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
2610                        "conference session=" + session);
2611                return;
2612            }
2613
2614            ImsCall.Listener listener;
2615
2616            synchronized(ImsCall.this) {
2617                listener = mListener;
2618            }
2619
2620            if (listener != null) {
2621                try {
2622                    listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2623                } catch (Throwable t) {
2624                    loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2625                }
2626            }
2627        }
2628
2629        @Override
2630        public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2631                ImsReasonInfo reasonInfo) {
2632            loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
2633
2634            if (isTransientConferenceSession(session)) {
2635                logi("callSessionRemoveParticipantsRequestFailed :: not supported for " +
2636                        "conference session=" + session);
2637                return;
2638            }
2639
2640            ImsCall.Listener listener;
2641
2642            synchronized(ImsCall.this) {
2643                listener = mListener;
2644            }
2645
2646            if (listener != null) {
2647                try {
2648                    listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2649                } catch (Throwable t) {
2650                    loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2651                }
2652            }
2653        }
2654
2655        @Override
2656        public void callSessionConferenceStateUpdated(ImsCallSession session,
2657                ImsConferenceState state) {
2658            logi("callSessionConferenceStateUpdated :: state=" + state);
2659
2660            conferenceStateUpdated(state);
2661        }
2662
2663        @Override
2664        public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2665                String ussdMessage) {
2666            logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
2667                    ussdMessage);
2668
2669            if (isTransientConferenceSession(session)) {
2670                logi("callSessionUssdMessageReceived :: not supported for transient " +
2671                        "conference session=" + session);
2672                return;
2673            }
2674
2675            ImsCall.Listener listener;
2676
2677            synchronized(ImsCall.this) {
2678                listener = mListener;
2679            }
2680
2681            if (listener != null) {
2682                try {
2683                    listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2684                } catch (Throwable t) {
2685                    loge("callSessionUssdMessageReceived :: ", t);
2686                }
2687            }
2688        }
2689
2690        @Override
2691        public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
2692            logi("callSessionTtyModeReceived :: mode=" + mode);
2693
2694            ImsCall.Listener listener;
2695
2696            synchronized(ImsCall.this) {
2697                listener = mListener;
2698            }
2699
2700            if (listener != null) {
2701                try {
2702                    listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
2703                } catch (Throwable t) {
2704                    loge("callSessionTtyModeReceived :: ", t);
2705                }
2706            }
2707        }
2708
2709        /**
2710         * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
2711         *
2712         * @param session The call session.
2713         * @param isMultiParty {@code true} if the session became multiparty, {@code false}
2714         *      otherwise.
2715         */
2716        @Override
2717        public void callSessionMultipartyStateChanged(ImsCallSession session,
2718                boolean isMultiParty) {
2719            if (VDBG) {
2720                logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
2721                        : "N"));
2722            }
2723
2724            ImsCall.Listener listener;
2725
2726            synchronized(ImsCall.this) {
2727                listener = mListener;
2728            }
2729
2730            if (listener != null) {
2731                try {
2732                    listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
2733                } catch (Throwable t) {
2734                    loge("callSessionMultipartyStateChanged :: ", t);
2735                }
2736            }
2737        }
2738
2739        public void callSessionHandover(ImsCallSession session, int srcAccessTech,
2740            int targetAccessTech, ImsReasonInfo reasonInfo) {
2741            logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
2742                srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
2743                reasonInfo);
2744
2745            ImsCall.Listener listener;
2746
2747            synchronized(ImsCall.this) {
2748                listener = mListener;
2749            }
2750
2751            if (listener != null) {
2752                try {
2753                    listener.onCallHandover(ImsCall.this, srcAccessTech, targetAccessTech,
2754                        reasonInfo);
2755                } catch (Throwable t) {
2756                    loge("callSessionHandover :: ", t);
2757                }
2758            }
2759        }
2760
2761        @Override
2762        public void callSessionHandoverFailed(ImsCallSession session, int srcAccessTech,
2763            int targetAccessTech, ImsReasonInfo reasonInfo) {
2764            loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" +
2765                srcAccessTech + ", targetAccessTech=" + targetAccessTech + ", reasonInfo=" +
2766                reasonInfo);
2767
2768            ImsCall.Listener listener;
2769
2770            synchronized(ImsCall.this) {
2771                listener = mListener;
2772            }
2773
2774            if (listener != null) {
2775                try {
2776                    listener.onCallHandoverFailed(ImsCall.this, srcAccessTech, targetAccessTech,
2777                        reasonInfo);
2778                } catch (Throwable t) {
2779                    loge("callSessionHandoverFailed :: ", t);
2780                }
2781            }
2782        }
2783    }
2784
2785    /**
2786     * Report a new conference state to the current {@link ImsCall} and inform listeners of the
2787     * change.  Marked as {@code VisibleForTesting} so that the
2788     * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
2789     * event package into a regular ongoing IMS call.
2790     *
2791     * @param state The {@link ImsConferenceState}.
2792     */
2793    @VisibleForTesting
2794    public void conferenceStateUpdated(ImsConferenceState state) {
2795        Listener listener;
2796
2797        synchronized(this) {
2798            notifyConferenceStateUpdated(state);
2799            listener = mListener;
2800        }
2801
2802        if (listener != null) {
2803            try {
2804                listener.onCallConferenceStateUpdated(this, state);
2805            } catch (Throwable t) {
2806                loge("callSessionConferenceStateUpdated :: ", t);
2807            }
2808        }
2809    }
2810
2811    /**
2812     * Provides a human-readable string representation of an update request.
2813     *
2814     * @param updateRequest The update request.
2815     * @return The string representation.
2816     */
2817    private String updateRequestToString(int updateRequest) {
2818        switch (updateRequest) {
2819            case UPDATE_NONE:
2820                return "NONE";
2821            case UPDATE_HOLD:
2822                return "HOLD";
2823            case UPDATE_HOLD_MERGE:
2824                return "HOLD_MERGE";
2825            case UPDATE_RESUME:
2826                return "RESUME";
2827            case UPDATE_MERGE:
2828                return "MERGE";
2829            case UPDATE_EXTEND_TO_CONFERENCE:
2830                return "EXTEND_TO_CONFERENCE";
2831            case UPDATE_UNSPECIFIED:
2832                return "UNSPECIFIED";
2833            default:
2834                return "UNKNOWN";
2835        }
2836    }
2837
2838    /**
2839     * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
2840     * severed at the same time.
2841     */
2842    private void clearMergeInfo() {
2843        if (CONF_DBG) {
2844            logi("clearMergeInfo :: clearing all merge info");
2845        }
2846
2847        // First clear out the merge partner then clear ourselves out.
2848        if (mMergeHost != null) {
2849            mMergeHost.mMergePeer = null;
2850            mMergeHost.mUpdateRequest = UPDATE_NONE;
2851            mMergeHost.mCallSessionMergePending = false;
2852        }
2853        if (mMergePeer != null) {
2854            mMergePeer.mMergeHost = null;
2855            mMergePeer.mUpdateRequest = UPDATE_NONE;
2856            mMergePeer.mCallSessionMergePending = false;
2857        }
2858        mMergeHost = null;
2859        mMergePeer = null;
2860        mUpdateRequest = UPDATE_NONE;
2861        mCallSessionMergePending = false;
2862    }
2863
2864    /**
2865     * Sets the merge peer for the current call.  The merge peer is the background call that will be
2866     * merged into this call.  On the merge peer, sets the merge host to be this call.
2867     *
2868     * @param mergePeer The peer call to be merged into this one.
2869     */
2870    private void setMergePeer(ImsCall mergePeer) {
2871        mMergePeer = mergePeer;
2872        mMergeHost = null;
2873
2874        mergePeer.mMergeHost = ImsCall.this;
2875        mergePeer.mMergePeer = null;
2876    }
2877
2878    /**
2879     * Sets the merge hody for the current call.  The merge host is the foreground call this call
2880     * will be merged into.  On the merge host, sets the merge peer to be this call.
2881     *
2882     * @param mergeHost The merge host this call will be merged into.
2883     */
2884    public void setMergeHost(ImsCall mergeHost) {
2885        mMergeHost = mergeHost;
2886        mMergePeer = null;
2887
2888        mergeHost.mMergeHost = null;
2889        mergeHost.mMergePeer = ImsCall.this;
2890    }
2891
2892    /**
2893     * Determines if the current call is in the process of merging with another call or conference.
2894     *
2895     * @return {@code true} if in the process of merging.
2896     */
2897    private boolean isMerging() {
2898        return mMergePeer != null || mMergeHost != null;
2899    }
2900
2901    /**
2902     * Determines if the current call is the host of the merge.
2903     *
2904     * @return {@code true} if the call is the merge host.
2905     */
2906    private boolean isMergeHost() {
2907        return mMergePeer != null && mMergeHost == null;
2908    }
2909
2910    /**
2911     * Determines if the current call is the peer of the merge.
2912     *
2913     * @return {@code true} if the call is the merge peer.
2914     */
2915    private boolean isMergePeer() {
2916        return mMergePeer == null && mMergeHost != null;
2917    }
2918
2919    /**
2920     * Determines if the call session is pending merge into a conference or not.
2921     *
2922     * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
2923     */
2924    private boolean isCallSessionMergePending() {
2925        return mCallSessionMergePending;
2926    }
2927
2928    /**
2929     * Sets flag indicating whether the call session is pending merge into a conference or not.
2930     *
2931     * @param callSessionMergePending {@code true} if a merge into the conference is pending,
2932     *      {@code false} otherwise.
2933     */
2934    private void setCallSessionMergePending(boolean callSessionMergePending) {
2935        mCallSessionMergePending = callSessionMergePending;
2936    }
2937
2938    /**
2939     * Determines if there is a conference merge in process.  If there is a merge in process,
2940     * determines if both the merge host and peer sessions have completed the merge process.  This
2941     * means that we have received terminate or hold signals for the sessions, indicating that they
2942     * are no longer in the process of being merged into the conference.
2943     * <p>
2944     * The sessions are considered to have merged if: both calls still have merge peer/host
2945     * relationships configured,  both sessions are not waiting to be merged into the conference,
2946     * and the transient conference session is alive in the case of an initial conference.
2947     *
2948     * @return {@code true} where the host and peer sessions have finished merging into the
2949     *      conference, {@code false} if the merge has not yet completed, and {@code false} if there
2950     *      is no conference merge in progress.
2951     */
2952    private boolean shouldProcessConferenceResult() {
2953        boolean areMergeTriggersDone = false;
2954
2955        synchronized (ImsCall.this) {
2956            // if there is a merge going on, then the merge host/peer relationships should have been
2957            // set up.  This works for both the initial conference or merging a call into an
2958            // existing conference.
2959            if (!isMergeHost() && !isMergePeer()) {
2960                if (CONF_DBG) {
2961                    loge("shouldProcessConferenceResult :: no merge in progress");
2962                }
2963                return false;
2964            }
2965
2966            // There is a merge in progress, so check the sessions to ensure:
2967            // 1. Both calls have completed being merged (or failing to merge) into the conference.
2968            // 2. The transient conference session is alive.
2969            if (isMergeHost()) {
2970                if (CONF_DBG) {
2971                    logi("shouldProcessConferenceResult :: We are a merge host");
2972                    logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer);
2973                }
2974                areMergeTriggersDone = !isCallSessionMergePending() &&
2975                        !mMergePeer.isCallSessionMergePending();
2976                if (!isMultiparty()) {
2977                    // Only check the transient session when there is no existing conference
2978                    areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession);
2979                }
2980            } else if (isMergePeer()) {
2981                if (CONF_DBG) {
2982                    logi("shouldProcessConferenceResult :: We are a merge peer");
2983                    logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost);
2984                }
2985                areMergeTriggersDone = !isCallSessionMergePending() &&
2986                        !mMergeHost.isCallSessionMergePending();
2987                if (!mMergeHost.isMultiparty()) {
2988                    // Only check the transient session when there is no existing conference
2989                    areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession);
2990                } else {
2991                    // This else block is a special case for Verizon to handle these steps
2992                    // 1. Establish a conference call.
2993                    // 2. Add a new call (conference in in BG)
2994                    // 3. Swap (conference active on FG)
2995                    // 4. Merge
2996                    // What happens here is that the BG call gets a terminated callback
2997                    // because it was added to the conference. I've seen where
2998                    // the FG gets no callback at all because its already active.
2999                    // So if we continue to wait for it to set its isCallSessionMerging
3000                    // flag to false...we'll be waiting forever.
3001                    areMergeTriggersDone = !isCallSessionMergePending();
3002                }
3003            } else {
3004                // Realistically this shouldn't happen, but best to be safe.
3005                loge("shouldProcessConferenceResult : merge in progress but call is neither" +
3006                        " host nor peer.");
3007            }
3008            if (CONF_DBG) {
3009                logi("shouldProcessConferenceResult :: returning:" +
3010                        (areMergeTriggersDone ? "true" : "false"));
3011            }
3012        }
3013        return areMergeTriggersDone;
3014    }
3015
3016    /**
3017     * Provides a string representation of the {@link ImsCall}.  Primarily intended for use in log
3018     * statements.
3019     *
3020     * @return String representation of call.
3021     */
3022    @Override
3023    public String toString() {
3024        StringBuilder sb = new StringBuilder();
3025        sb.append("[ImsCall objId:");
3026        sb.append(System.identityHashCode(this));
3027        sb.append(" onHold:");
3028        sb.append(isOnHold() ? "Y" : "N");
3029        sb.append(" mute:");
3030        sb.append(isMuted() ? "Y" : "N");
3031        sb.append(" updateRequest:");
3032        sb.append(updateRequestToString(mUpdateRequest));
3033        sb.append(" merging:");
3034        sb.append(isMerging() ? "Y" : "N");
3035        if (isMerging()) {
3036            if (isMergePeer()) {
3037                sb.append("P");
3038            } else {
3039                sb.append("H");
3040            }
3041        }
3042        sb.append(" merge action pending:");
3043        sb.append(isCallSessionMergePending() ? "Y" : "N");
3044        sb.append(" merged:");
3045        sb.append(isMerged() ? "Y" : "N");
3046        sb.append(" multiParty:");
3047        sb.append(isMultiparty() ? "Y" : "N");
3048        sb.append(" confHost:");
3049        sb.append(isConferenceHost() ? "Y" : "N");
3050        sb.append(" buried term:");
3051        sb.append(mSessionEndDuringMerge ? "Y" : "N");
3052        sb.append(" session:");
3053        sb.append(mSession);
3054        sb.append(" transientSession:");
3055        sb.append(mTransientConferenceSession);
3056        sb.append("]");
3057        return sb.toString();
3058    }
3059
3060    private void throwImsException(Throwable t, int code) throws ImsException {
3061        if (t instanceof ImsException) {
3062            throw (ImsException) t;
3063        } else {
3064            throw new ImsException(String.valueOf(code), t, code);
3065        }
3066    }
3067
3068    /**
3069     * Append the ImsCall information to the provided string. Usefull for as a logging helper.
3070     * @param s The original string
3071     * @return The original string with {@code ImsCall} information appended to it.
3072     */
3073    private String appendImsCallInfoToString(String s) {
3074        StringBuilder sb = new StringBuilder();
3075        sb.append(s);
3076        sb.append(" ImsCall=");
3077        sb.append(ImsCall.this);
3078        return sb.toString();
3079    }
3080
3081    /**
3082     * Log a string to the radio buffer at the info level.
3083     * @param s The message to log
3084     */
3085    private void logi(String s) {
3086        Log.i(TAG, appendImsCallInfoToString(s));
3087    }
3088
3089    /**
3090     * Log a string to the radio buffer at the debug level.
3091     * @param s The message to log
3092     */
3093    private void logd(String s) {
3094        Log.d(TAG, appendImsCallInfoToString(s));
3095    }
3096
3097    /**
3098     * Log a string to the radio buffer at the verbose level.
3099     * @param s The message to log
3100     */
3101    private void logv(String s) {
3102        Log.v(TAG, appendImsCallInfoToString(s));
3103    }
3104
3105    /**
3106     * Log a string to the radio buffer at the error level.
3107     * @param s The message to log
3108     */
3109    private void loge(String s) {
3110        Log.e(TAG, appendImsCallInfoToString(s));
3111    }
3112
3113    /**
3114     * Log a string to the radio buffer at the error level with a throwable
3115     * @param s The message to log
3116     * @param t The associated throwable
3117     */
3118    private void loge(String s, Throwable t) {
3119        Log.e(TAG, appendImsCallInfoToString(s), t);
3120    }
3121
3122}
3123