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