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