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