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