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