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