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