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