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