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