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