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