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