• 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(), mContext.getMainExecutor());
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(), mContext.getMainExecutor());
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(), mContext.getMainExecutor());
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, ignoring");
1766                 return;
1767             }
1768             if (mCallProfile == null || mCallProfile.mMediaProfile == null)  {
1769                 loge("sendRttMessage:: no valid call profile, ignoring");
1770                 return;
1771             }
1772             if (!mCallProfile.mMediaProfile.isRttCall()) {
1773                 logi("sendRttMessage::Not an rtt call, ignoring");
1774                 return;
1775             }
1776             mSession.sendRttMessage(rttMessage);
1777         }
1778     }
1779 
1780     /**
1781      * Sends a user-requested RTT upgrade request.
1782      * @param rttOn true if the request is to turn on RTT, false to turn off.
1783      */
sendRttModifyRequest(boolean rttOn)1784     public void sendRttModifyRequest(boolean rttOn) {
1785         logi("sendRttModifyRequest");
1786 
1787         synchronized(mLockObj) {
1788             if (mSession == null) {
1789                 loge("sendRttModifyRequest::no session, ignoring");
1790                 return;
1791             }
1792             if (mCallProfile == null || mCallProfile.mMediaProfile == null)  {
1793                 loge("sendRttModifyRequest:: no valid call profile, ignoring");
1794                 return;
1795             }
1796             if (rttOn && mCallProfile.mMediaProfile.isRttCall()) {
1797                 logi("sendRttModifyRequest::Already RTT call, ignoring request to turn on.");
1798                 return;
1799             } else if (!rttOn && !mCallProfile.mMediaProfile.isRttCall()) {
1800                 logi("sendRttModifyRequest::Not RTT call, ignoring request to turn off.");
1801                 return;
1802             }
1803             // Make a copy of the current ImsCallProfile and modify it to enable RTT
1804             Parcel p = Parcel.obtain();
1805             mCallProfile.writeToParcel(p, 0);
1806             p.setDataPosition(0);
1807             ImsCallProfile requestedProfile = new ImsCallProfile(p);
1808             requestedProfile.mMediaProfile.setRttMode(rttOn
1809                     ? ImsStreamMediaProfile.RTT_MODE_FULL
1810                     : ImsStreamMediaProfile.RTT_MODE_DISABLED);
1811 
1812             mSession.sendRttModifyRequest(requestedProfile);
1813         }
1814     }
1815 
1816     /**
1817      * Sends the user's response to a remotely-issued RTT upgrade request
1818      *
1819      * @param textStream A valid {@link Connection.RttTextStream} if the user
1820      *                   accepts, {@code null} if not.
1821      */
sendRttModifyResponse(boolean status)1822     public void sendRttModifyResponse(boolean status) {
1823         logi("sendRttModifyResponse");
1824 
1825         synchronized(mLockObj) {
1826             if (mSession == null) {
1827                 loge("sendRttModifyResponse::no session");
1828                 return;
1829             }
1830             if (mCallProfile == null || mCallProfile.mMediaProfile == null)  {
1831                 loge("sendRttModifyResponse:: no valid call profile, ignoring");
1832                 return;
1833             }
1834             if (mCallProfile.mMediaProfile.isRttCall()) {
1835                 logi("sendRttModifyResponse::Already RTT call, ignoring.");
1836                 return;
1837             }
1838             mSession.sendRttModifyResponse(status);
1839         }
1840     }
1841 
1842     /**
1843      * Requests that RTP header extensions are added to the next RTP packet sent by the IMS stack.
1844      * <p>
1845      * The {@link RtpHeaderExtension#getLocalIdentifier()} local identifiers specified here must match
1846      * agreed upon identifiers as indicated in
1847      * {@link ImsCallProfile#getAcceptedRtpHeaderExtensionTypes()} for the current
1848      * {@link #getCallProfile()}.
1849      * <p>
1850      * By specification, the RTP header extension is an unacknowledged transmission and there is no
1851      * guarantee that the header extension will be delivered by the network to the other end of the
1852      * call.
1853      * @param rtpHeaderExtensions The RTP header extension(s) to be included in the next RTP
1854      *                            packet.
1855      */
sendRtpHeaderExtensions(@onNull Set<RtpHeaderExtension> rtpHeaderExtensions)1856     public void sendRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
1857         logi("sendRtpHeaderExtensions; extensionsSent=" + rtpHeaderExtensions.size());
1858         synchronized(mLockObj) {
1859             if (mSession == null) {
1860                 loge("sendRtpHeaderExtensions::no session");
1861             }
1862             mSession.sendRtpHeaderExtensions(rtpHeaderExtensions);
1863         }
1864     }
1865 
setAnswerWithRtt()1866     public void setAnswerWithRtt() {
1867         mAnswerWithRtt = true;
1868     }
1869 
clear(ImsReasonInfo lastReasonInfo)1870     private void clear(ImsReasonInfo lastReasonInfo) {
1871         mInCall = false;
1872         mHold = false;
1873         mUpdateRequest = UPDATE_NONE;
1874         mLastReasonInfo = lastReasonInfo;
1875     }
1876 
1877     /**
1878      * Creates an IMS call session listener.
1879      */
createCallSessionListener()1880     private ImsCallSession.Listener createCallSessionListener() {
1881         mImsCallSessionListenerProxy = new ImsCallSessionListenerProxy();
1882         return mImsCallSessionListenerProxy;
1883     }
1884 
1885     /**
1886      * @return the current ImsCallSessionListenerProxy.  NOTE: ONLY FOR USE WITH TESTING.
1887      */
1888     @VisibleForTesting
getImsCallSessionListenerProxy()1889     public ImsCallSessionListenerProxy getImsCallSessionListenerProxy() {
1890         return mImsCallSessionListenerProxy;
1891     }
1892 
1893     /**
1894      * @return the current Listener.  NOTE: ONLY FOR USE WITH TESTING.
1895      */
1896     @VisibleForTesting
getListener()1897     public Listener getListener() {
1898         return mListener;
1899     }
1900 
createNewCall(ImsCallSession session, ImsCallProfile profile)1901     private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1902         ImsCall call = new ImsCall(mContext, profile);
1903 
1904         try {
1905             call.attachSession(session);
1906         } catch (ImsException e) {
1907             if (call != null) {
1908                 call.close();
1909                 call = null;
1910             }
1911         }
1912 
1913         // Do additional operations...
1914 
1915         return call;
1916     }
1917 
createHoldMediaProfile()1918     private ImsStreamMediaProfile createHoldMediaProfile() {
1919         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1920 
1921         if (mCallProfile == null) {
1922             return mediaProfile;
1923         }
1924 
1925         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1926         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1927         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1928 
1929         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1930             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1931         }
1932 
1933         return mediaProfile;
1934     }
1935 
createResumeMediaProfile()1936     private ImsStreamMediaProfile createResumeMediaProfile() {
1937         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1938 
1939         if (mCallProfile == null) {
1940             return mediaProfile;
1941         }
1942 
1943         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1944         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1945         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1946 
1947         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1948             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1949         }
1950 
1951         return mediaProfile;
1952     }
1953 
enforceConversationMode()1954     private void enforceConversationMode() {
1955         if (mInCall) {
1956             mHold = false;
1957             mUpdateRequest = UPDATE_NONE;
1958         }
1959     }
1960 
mergeInternal()1961     private void mergeInternal() {
1962         if (CONF_DBG) {
1963             logi("mergeInternal :: ");
1964         }
1965 
1966         mSession.merge();
1967         mUpdateRequest = UPDATE_MERGE;
1968     }
1969 
notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo)1970     private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1971         ImsCall.Listener listener = mListener;
1972         clear(reasonInfo);
1973 
1974         if (listener != null) {
1975             try {
1976                 listener.onCallTerminated(this, reasonInfo);
1977             } catch (Throwable t) {
1978                 loge("notifyConferenceSessionTerminated :: ", t);
1979             }
1980         }
1981     }
1982 
notifyConferenceStateUpdated(ImsConferenceState state)1983     private void notifyConferenceStateUpdated(ImsConferenceState state) {
1984         if (state == null || state.mParticipants == null) {
1985             return;
1986         }
1987 
1988         mConferenceParticipants = parseConferenceState(state);
1989 
1990         if (mConferenceParticipants != null && mListener != null) {
1991             try {
1992                 mListener.onConferenceParticipantsStateChanged(this, mConferenceParticipants);
1993             } catch (Throwable t) {
1994                 loge("notifyConferenceStateUpdated :: ", t);
1995             }
1996         }
1997     }
1998 
parseConferenceState(ImsConferenceState state)1999     public static List<ConferenceParticipant> parseConferenceState(ImsConferenceState state) {
2000         Set<Entry<String, Bundle>> participants = state.mParticipants.entrySet();
2001 
2002         if (participants == null) {
2003             return Collections.emptyList();
2004         }
2005 
2006         Iterator<Entry<String, Bundle>> iterator = participants.iterator();
2007         List<ConferenceParticipant> conferenceParticipants = new ArrayList<>(participants.size());
2008         while (iterator.hasNext()) {
2009             Entry<String, Bundle> entry = iterator.next();
2010 
2011             String key = entry.getKey();
2012             Bundle confInfo = entry.getValue();
2013             String status = confInfo.getString(ImsConferenceState.STATUS);
2014             String user = confInfo.getString(ImsConferenceState.USER);
2015             String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT);
2016             String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
2017 
2018             if (CONF_DBG) {
2019                 Log.i(TAG, "notifyConferenceStateUpdated :: key=" + Rlog.pii(TAG, key) +
2020                         ", status=" + status +
2021                         ", user=" + Rlog.pii(TAG, user) +
2022                         ", displayName= " + Rlog.pii(TAG, displayName) +
2023                         ", endpoint=" + Rlog.pii(TAG, endpoint));
2024             }
2025 
2026             Uri handle = Uri.parse(user);
2027             if (endpoint == null) {
2028                 endpoint = "";
2029             }
2030             Uri endpointUri = Uri.parse(endpoint);
2031             int connectionState = ImsConferenceState.getConnectionStateForStatus(status);
2032 
2033             if (connectionState != Connection.STATE_DISCONNECTED) {
2034                 ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle,
2035                         displayName, endpointUri, connectionState, Call.Details.DIRECTION_UNKNOWN);
2036                 conferenceParticipants.add(conferenceParticipant);
2037             }
2038         }
2039         return conferenceParticipants;
2040     }
2041 
2042     /**
2043      * Perform all cleanup and notification around the termination of a session.
2044      * Note that there are 2 distinct modes of operation.  The first is when
2045      * we receive a session termination on the primary session when we are
2046      * in the processing of merging.  The second is when we are not merging anything
2047      * and the call is terminated.
2048      *
2049      * @param reasonInfo The reason for the session termination
2050      */
processCallTerminated(ImsReasonInfo reasonInfo)2051     private void processCallTerminated(ImsReasonInfo reasonInfo) {
2052         logi("processCallTerminated :: reason=" + reasonInfo + " userInitiated = " +
2053                 mTerminationRequestPending);
2054 
2055         ImsCall.Listener listener = null;
2056         synchronized(ImsCall.this) {
2057             // If we are in the midst of establishing a conference, we will bury the termination
2058             // until the merge has completed.  If necessary we can surface the termination at
2059             // this point.
2060             // We will also NOT bury the termination if a termination was initiated locally.
2061             if (isCallSessionMergePending() && !mTerminationRequestPending) {
2062                 // Since we are in the process of a merge, this trigger means something
2063                 // else because it is probably due to the merge happening vs. the
2064                 // session is really terminated. Let's flag this and revisit if
2065                 // the merge() ends up failing because we will need to take action on the
2066                 // mSession in that case since the termination was not due to the merge
2067                 // succeeding.
2068                 if (CONF_DBG) {
2069                     logi("processCallTerminated :: burying termination during ongoing merge.");
2070                 }
2071                 mSessionEndDuringMerge = true;
2072                 mSessionEndDuringMergeReasonInfo = reasonInfo;
2073                 return;
2074             }
2075 
2076             // If we are terminating the conference call, notify using conference listeners.
2077             if (isMultiparty()) {
2078                 notifyConferenceSessionTerminated(reasonInfo);
2079                 return;
2080             } else {
2081                 listener = mListener;
2082                 clear(reasonInfo);
2083             }
2084         }
2085 
2086         if (listener != null) {
2087             try {
2088                 listener.onCallTerminated(ImsCall.this, reasonInfo);
2089             } catch (Throwable t) {
2090                 loge("processCallTerminated :: ", t);
2091             }
2092         }
2093     }
2094 
2095     /**
2096      * This function determines if the ImsCallSession is our actual ImsCallSession or if is
2097      * the transient session used in the process of creating a conference. This function should only
2098      * be called within  callbacks that are not directly related to conference merging but might
2099      * potentially still be called on the transient ImsCallSession sent to us from
2100      * callSessionMergeStarted() when we don't really care. In those situations, we probably don't
2101      * want to take any action so we need to know that we can return early.
2102      *
2103      * @param session - The {@link ImsCallSession} that the function needs to analyze
2104      * @return true if this is the transient {@link ImsCallSession}, false otherwise.
2105      */
isTransientConferenceSession(ImsCallSession session)2106     private boolean isTransientConferenceSession(ImsCallSession session) {
2107         if (session != null && session != mSession && session == mTransientConferenceSession) {
2108             return true;
2109         }
2110         return false;
2111     }
2112 
setTransientSessionAsPrimary(ImsCallSession transientSession)2113     private void setTransientSessionAsPrimary(ImsCallSession transientSession) {
2114         synchronized (ImsCall.this) {
2115             mSession.setListener(null, null);
2116             mSession = transientSession;
2117             mSession.setListener(createCallSessionListener(), mContext.getMainExecutor());
2118         }
2119     }
2120 
markCallAsMerged(boolean playDisconnectTone)2121     private void markCallAsMerged(boolean playDisconnectTone) {
2122         if (!isSessionAlive(mSession)) {
2123             // If the peer is dead, let's not play a disconnect sound for it when we
2124             // unbury the termination callback.
2125             logi("markCallAsMerged");
2126             setIsMerged(playDisconnectTone);
2127             mSessionEndDuringMerge = true;
2128             String reasonInfo;
2129             int reasonCode = ImsReasonInfo.CODE_UNSPECIFIED;
2130             if (playDisconnectTone) {
2131                 reasonCode = ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE;
2132                 reasonInfo = "Call ended by network";
2133             } else {
2134                 reasonCode = ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE;
2135                 reasonInfo = "Call ended during conference merge process.";
2136             }
2137             mSessionEndDuringMergeReasonInfo = new ImsReasonInfo(
2138                     reasonCode, 0, reasonInfo);
2139         }
2140     }
2141 
2142     /**
2143      * Checks if the merge was requested by foreground conference call
2144      *
2145      * @return true if the merge was requested by foreground conference call
2146      */
isMergeRequestedByConf()2147     public boolean isMergeRequestedByConf() {
2148         synchronized(mLockObj) {
2149             return mMergeRequestedByConference;
2150         }
2151     }
2152 
2153     /**
2154      * Resets the flag which indicates merge request was sent by
2155      * foreground conference call
2156      */
resetIsMergeRequestedByConf(boolean value)2157     public void resetIsMergeRequestedByConf(boolean value) {
2158         synchronized(mLockObj) {
2159             mMergeRequestedByConference = value;
2160         }
2161     }
2162 
2163     /**
2164      * Returns current ImsCallSession
2165      *
2166      * @return current session
2167      */
getSession()2168     public ImsCallSession getSession() {
2169         synchronized(mLockObj) {
2170             return mSession;
2171         }
2172     }
2173 
2174     /**
2175      * We have detected that a initial conference call has been fully configured. The internal
2176      * state of both {@code ImsCall} objects need to be cleaned up to reflect the new state.
2177      * This function should only be called in the context of the merge host to simplify logic
2178      *
2179      */
processMergeComplete()2180     private void processMergeComplete() {
2181         logi("processMergeComplete :: ");
2182 
2183         // The logic simplifies if we can assume that this function is only called on
2184         // the merge host.
2185         if (!isMergeHost()) {
2186             loge("processMergeComplete :: We are not the merge host!");
2187             return;
2188         }
2189 
2190         ImsCall.Listener listener;
2191         boolean swapRequired = false;
2192 
2193         ImsCall finalHostCall;
2194         ImsCall finalPeerCall;
2195 
2196         synchronized(ImsCall.this) {
2197             if (isMultiparty()) {
2198                 setIsMerged(false);
2199                 // if case handles Case 4 explained in callSessionMergeComplete
2200                 // otherwise it is case 5
2201                 if (!mMergeRequestedByConference) {
2202                     // single call in fg, conference call in bg.
2203                     // Finally conf call becomes active after conference
2204                     this.mHold = false;
2205                     swapRequired = true;
2206                 }
2207                 mMergePeer.markCallAsMerged(false);
2208                 finalHostCall = this;
2209                 finalPeerCall = mMergePeer;
2210             } else {
2211                 // If we are here, we are not trying to merge a new call into an existing
2212                 // conference.  That means that there is a transient session on the merge
2213                 // host that represents the future conference once all the parties
2214                 // have been added to it.  So make sure that it exists or else something
2215                 // very wrong is going on.
2216                 if (mTransientConferenceSession == null) {
2217                     loge("processMergeComplete :: No transient session!");
2218                     return;
2219                 }
2220                 if (mMergePeer == null) {
2221                     loge("processMergeComplete :: No merge peer!");
2222                     return;
2223                 }
2224 
2225                 // Since we are the host, we have the transient session attached to us. Let's detach
2226                 // it and figure out where we need to set it for the final conference configuration.
2227                 ImsCallSession transientConferenceSession = mTransientConferenceSession;
2228                 mTransientConferenceSession = null;
2229 
2230                 // Clear the listener for this transient session, we'll create a new listener
2231                 // when it is attached to the final ImsCall that it should live on.
2232                 transientConferenceSession.setListener(null, null);
2233 
2234                 // Determine which call the transient session should be moved to.  If the current
2235                 // call session is still alive and the merge peer's session is not, we have a
2236                 // situation where the current call failed to merge into the conference but the
2237                 // merge peer did merge in to the conference.  In this type of scenario the current
2238                 // call will continue as a single party call, yet the background call will become
2239                 // the conference.
2240 
2241                 // handles Case 3 explained in callSessionMergeComplete
2242                 if (isSessionAlive(mSession) && !isSessionAlive(mMergePeer.getCallSession())) {
2243                     // I'm the host but we are moving the transient session to the peer since its
2244                     // session was disconnected and my session is still alive.  This signifies that
2245                     // their session was properly added to the conference but mine was not because
2246                     // it is probably in the held state as opposed to part of the final conference.
2247                     // In this case, we need to set isMerged to false on both calls so the
2248                     // disconnect sound is called when either call disconnects.
2249                     // Note that this case is only valid if this is an initial conference being
2250                     // brought up.
2251                     mMergePeer.mHold = false;
2252                     this.mHold = true;
2253                     if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
2254                         mMergePeer.mConferenceParticipants = mConferenceParticipants;
2255                     }
2256                     // At this point both host & peer will have participant information.
2257                     // Peer will transition to host & the participant information
2258                     // from that will be used
2259                     // HostCall that failed to merge will remain as a single call with
2260                     // mConferenceParticipants, which should not be used.
2261                     // Expectation is that if this call becomes part of a conference call in future,
2262                     // mConferenceParticipants will be overriten with new CEP that is received.
2263                     finalHostCall = mMergePeer;
2264                     finalPeerCall = this;
2265                     swapRequired = true;
2266                     setIsMerged(false);
2267                     mMergePeer.setIsMerged(false);
2268                     if (CONF_DBG) {
2269                         logi("processMergeComplete :: transient will transfer to merge peer");
2270                     }
2271                 } else if (!isSessionAlive(mSession) &&
2272                                 isSessionAlive(mMergePeer.getCallSession())) {
2273                     // Handles case 2 explained in callSessionMergeComplete
2274                     // The transient session stays with us and the disconnect sound should be played
2275                     // when the merge peer eventually disconnects since it was not actually added to
2276                     // the conference and is probably sitting in the held state.
2277                     finalHostCall = this;
2278                     finalPeerCall = mMergePeer;
2279                     swapRequired = false;
2280                     setIsMerged(false);
2281                     mMergePeer.setIsMerged(false); // Play the disconnect sound
2282                     if (CONF_DBG) {
2283                         logi("processMergeComplete :: transient will stay with the merge host");
2284                     }
2285                 } else {
2286                     // Handles case 1 explained in callSessionMergeComplete
2287                     // The transient session stays with us and the disconnect sound should not be
2288                     // played when we ripple up the disconnect for the merge peer because it was
2289                     // only disconnected to be added to the conference.
2290                     finalHostCall = this;
2291                     finalPeerCall = mMergePeer;
2292                     mMergePeer.markCallAsMerged(false);
2293                     swapRequired = false;
2294                     setIsMerged(false);
2295                     mMergePeer.setIsMerged(true);
2296                     if (CONF_DBG) {
2297                         logi("processMergeComplete :: transient will stay with us (I'm the host).");
2298                     }
2299                 }
2300 
2301                 if (CONF_DBG) {
2302                     logi("processMergeComplete :: call=" + finalHostCall + " is the final host");
2303                 }
2304 
2305                 // Add the transient session to the ImsCall that ended up being the host for the
2306                 // conference.
2307                 finalHostCall.setTransientSessionAsPrimary(transientConferenceSession);
2308             }
2309 
2310             listener = finalHostCall.mListener;
2311 
2312             updateCallProfile(finalPeerCall);
2313             updateCallProfile(finalHostCall);
2314 
2315             // Clear all the merge related flags.
2316             clearMergeInfo();
2317 
2318             // For the final peer...let's bubble up any possible disconnects that we had
2319             // during the merge process
2320             finalPeerCall.notifySessionTerminatedDuringMerge();
2321             // For the final host, let's just bury the disconnects that we my have received
2322             // during the merge process since we are now the host of the conference call.
2323             finalHostCall.clearSessionTerminationFlags();
2324 
2325             // Keep track of the fact that merge host is the origin of a conference call in
2326             // progress.  This is important so that we can later determine if a multiparty ImsCall
2327             // is multiparty because it was the origin of a conference call, or because it is a
2328             // member of a conference on another device.
2329             finalHostCall.mIsConferenceHost = true;
2330         }
2331         if (listener != null) {
2332             try {
2333                 // finalPeerCall will have the participant that was not merged and
2334                 // it will be held state
2335                 // if peer was merged successfully, finalPeerCall will be null
2336                 listener.onCallMerged(finalHostCall, finalPeerCall, swapRequired);
2337             } catch (Throwable t) {
2338                 loge("processMergeComplete :: ", t);
2339             }
2340             if (mConferenceParticipants != null && !mConferenceParticipants.isEmpty()) {
2341                 try {
2342                     listener.onConferenceParticipantsStateChanged(finalHostCall,
2343                             mConferenceParticipants);
2344                 } catch (Throwable t) {
2345                     loge("processMergeComplete :: ", t);
2346                 }
2347             }
2348         }
2349         return;
2350     }
2351 
updateCallProfile(ImsCall call)2352     private static void updateCallProfile(ImsCall call) {
2353         if (call != null) {
2354             call.updateCallProfile();
2355         }
2356     }
2357 
updateCallProfile()2358     private void updateCallProfile() {
2359         synchronized (mLockObj) {
2360             if (mSession != null) {
2361                 setCallProfile(mSession.getCallProfile());
2362             }
2363         }
2364     }
2365 
2366     /**
2367      * Handles the case where the session has ended during a merge by reporting the termination
2368      * reason to listeners.
2369      */
notifySessionTerminatedDuringMerge()2370     private void notifySessionTerminatedDuringMerge() {
2371         ImsCall.Listener listener;
2372         boolean notifyFailure = false;
2373         ImsReasonInfo notifyFailureReasonInfo = null;
2374 
2375         synchronized(ImsCall.this) {
2376             listener = mListener;
2377             if (mSessionEndDuringMerge) {
2378                 // Set some local variables that will send out a notification about a
2379                 // previously buried termination callback for our primary session now that
2380                 // we know that this is not due to the conference call merging successfully.
2381                 if (CONF_DBG) {
2382                     logi("notifySessionTerminatedDuringMerge ::reporting terminate during merge");
2383                 }
2384                 notifyFailure = true;
2385                 notifyFailureReasonInfo = mSessionEndDuringMergeReasonInfo;
2386             }
2387             clearSessionTerminationFlags();
2388         }
2389 
2390         if (listener != null && notifyFailure) {
2391             try {
2392                 processCallTerminated(notifyFailureReasonInfo);
2393             } catch (Throwable t) {
2394                 loge("notifySessionTerminatedDuringMerge :: ", t);
2395             }
2396         }
2397     }
2398 
clearSessionTerminationFlags()2399     private void clearSessionTerminationFlags() {
2400         mSessionEndDuringMerge = false;
2401         mSessionEndDuringMergeReasonInfo = null;
2402     }
2403 
2404    /**
2405      * We received a callback from ImsCallSession that a merge failed. Clean up all
2406      * internal state to represent this state change.  The calling function is a callback
2407      * and should have been called on the session that was in the foreground
2408      * when merge() was originally called.  It is assumed that this function will be called
2409      * on the merge host.
2410      *
2411      * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
2412      */
processMergeFailed(ImsReasonInfo reasonInfo)2413     private void processMergeFailed(ImsReasonInfo reasonInfo) {
2414         logi("processMergeFailed :: reason=" + reasonInfo);
2415 
2416         ImsCall.Listener listener;
2417         synchronized(ImsCall.this) {
2418             // The logic simplifies if we can assume that this function is only called on
2419             // the merge host.
2420             if (!isMergeHost()) {
2421                 loge("processMergeFailed :: We are not the merge host!");
2422                 return;
2423             }
2424 
2425             // Try to clean up the transient session if it exists.
2426             if (mTransientConferenceSession != null) {
2427                 mTransientConferenceSession.setListener(null, null);
2428                 mTransientConferenceSession = null;
2429             }
2430 
2431             listener = mListener;
2432 
2433             // Ensure the calls being conferenced into the conference has isMerged = false.
2434             // Ensure any terminations are surfaced from this session.
2435             markCallAsMerged(true);
2436             setCallSessionMergePending(false);
2437             notifySessionTerminatedDuringMerge();
2438 
2439             // Perform the same cleanup on the merge peer if it exists.
2440             if (mMergePeer != null) {
2441                 mMergePeer.markCallAsMerged(true);
2442                 mMergePeer.setCallSessionMergePending(false);
2443                 mMergePeer.notifySessionTerminatedDuringMerge();
2444             } else {
2445                 loge("processMergeFailed :: No merge peer!");
2446             }
2447 
2448             // Clear all the various flags around coordinating this merge.
2449             clearMergeInfo();
2450         }
2451         if (listener != null) {
2452             try {
2453                 listener.onCallMergeFailed(ImsCall.this, reasonInfo);
2454             } catch (Throwable t) {
2455                 loge("processMergeFailed :: ", t);
2456             }
2457         }
2458 
2459         return;
2460     }
2461 
2462     @VisibleForTesting
2463     public class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
2464         @Override
callSessionInitiating(ImsCallSession session, ImsCallProfile profile)2465         public void callSessionInitiating(ImsCallSession session, ImsCallProfile profile) {
2466             logi("callSessionInitiating :: session=" + session + " profile=" + profile);
2467             if (isTransientConferenceSession(session)) {
2468                 // If it is a transient (conference) session, there is no action for this signal.
2469                 logi("callSessionInitiating :: not supported for transient conference session=" +
2470                         session);
2471                 return;
2472             }
2473 
2474             ImsCall.Listener listener;
2475 
2476             synchronized(ImsCall.this) {
2477                 listener = mListener;
2478                 setCallProfile(profile);
2479             }
2480 
2481             if (listener != null) {
2482                 try {
2483                     listener.onCallInitiating(ImsCall.this);
2484                 } catch (Throwable t) {
2485                     loge("callSessionInitiating :: ", t);
2486                 }
2487             }
2488         }
2489 
2490         @Override
callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile)2491         public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
2492             logi("callSessionProgressing :: session=" + session + " profile=" + profile);
2493 
2494             if (isTransientConferenceSession(session)) {
2495                 // If it is a transient (conference) session, there is no action for this signal.
2496                 logi("callSessionProgressing :: not supported for transient conference session=" +
2497                         session);
2498                 return;
2499             }
2500 
2501             ImsCall.Listener listener;
2502 
2503             ImsCallProfile updatedProfile = session.getCallProfile();
2504             synchronized(ImsCall.this) {
2505                 listener = mListener;
2506                 // The ImsCallProfile may have updated here (for example call state change). Query
2507                 // the potentially updated call profile to pick up these changes.
2508                 setCallProfile(updatedProfile);
2509                 // Apply the new mediaProfile on top of the Call Profile so it is not ignored in
2510                 // case the ImsService has not had a chance to update it yet.
2511                 mCallProfile.mMediaProfile.copyFrom(profile);
2512             }
2513 
2514             if (listener != null) {
2515                 try {
2516                     listener.onCallProgressing(ImsCall.this);
2517                 } catch (Throwable t) {
2518                     loge("callSessionProgressing :: ", t);
2519                 }
2520             }
2521         }
2522 
2523         @Override
callSessionStarted(ImsCallSession session, ImsCallProfile profile)2524         public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
2525             logi("callSessionStarted :: session=" + session + " profile=" + profile);
2526 
2527             if (!isTransientConferenceSession(session)) {
2528                 // In the case that we are in the middle of a merge (either host or peer), we have
2529                 // closure as far as this call's primary session is concerned.  If we are not
2530                 // merging...its a NOOP.
2531                 setCallSessionMergePending(false);
2532             } else {
2533                 logi("callSessionStarted :: on transient session=" + session);
2534                 return;
2535             }
2536 
2537             if (isTransientConferenceSession(session)) {
2538                 // No further processing is needed if this is the transient session.
2539                 return;
2540             }
2541 
2542             ImsCall.Listener listener;
2543 
2544             synchronized(ImsCall.this) {
2545                 listener = mListener;
2546                 setCallProfile(profile);
2547             }
2548 
2549             if (listener != null) {
2550                 try {
2551                     listener.onCallStarted(ImsCall.this);
2552                 } catch (Throwable t) {
2553                     loge("callSessionStarted :: ", t);
2554                 }
2555             }
2556         }
2557 
2558         @Override
callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2559         public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2560             loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2561 
2562             if (isTransientConferenceSession(session)) {
2563                 // We should not get this callback for a transient session.
2564                 logi("callSessionStartFailed :: not supported for transient conference session=" +
2565                         session);
2566                 return;
2567             }
2568 
2569             if (mIsConferenceHost) {
2570                 // If the dial request was a adhoc conf calling one, this call would have
2571                 // been marked the conference host as part of the request.
2572                 mIsConferenceHost = false;
2573             }
2574 
2575             ImsCall.Listener listener;
2576 
2577             synchronized(ImsCall.this) {
2578                 listener = mListener;
2579                 mLastReasonInfo = reasonInfo;
2580             }
2581 
2582             if (listener != null) {
2583                 try {
2584                     listener.onCallStartFailed(ImsCall.this, reasonInfo);
2585                 } catch (Throwable t) {
2586                     loge("callSessionStarted :: ", t);
2587                 }
2588             }
2589         }
2590 
2591         @Override
callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo)2592         public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
2593             logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo);
2594 
2595             if (isTransientConferenceSession(session)) {
2596                 logi("callSessionTerminated :: on transient session=" + session);
2597                 // This is bad, it should be treated much a callSessionMergeFailed since the
2598                 // transient session only exists when in the process of a merge and the
2599                 // termination of this session is effectively the end of the merge.
2600                 processMergeFailed(reasonInfo);
2601                 return;
2602             }
2603 
2604             if (mOverrideReason != ImsReasonInfo.CODE_UNSPECIFIED) {
2605                 logi("callSessionTerminated :: overrideReasonInfo=" + mOverrideReason);
2606                 reasonInfo = new ImsReasonInfo(mOverrideReason, reasonInfo.getExtraCode(),
2607                         reasonInfo.getExtraMessage());
2608             }
2609 
2610             // Process the termination first.  If we are in the midst of establishing a conference
2611             // call, we may bury this callback until we are done.  If there so no conference
2612             // call, the code after this function will be a NOOP.
2613             processCallTerminated(reasonInfo);
2614 
2615             // If session has terminated, it is no longer pending merge.
2616             setCallSessionMergePending(false);
2617 
2618         }
2619 
2620         @Override
callSessionHeld(ImsCallSession session, ImsCallProfile profile)2621         public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
2622             logi("callSessionHeld :: session=" + session + "profile=" + profile);
2623             ImsCall.Listener listener;
2624 
2625             synchronized(ImsCall.this) {
2626                 // If the session was held, it is no longer pending a merge -- this means it could
2627                 // not be merged into the conference and was held instead.
2628                 setCallSessionMergePending(false);
2629 
2630                 setCallProfile(profile);
2631 
2632                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2633                     // This hold request was made to set the stage for a merge.
2634                     mergeInternal();
2635                     return;
2636                 }
2637 
2638                 mHold = true;
2639                 mUpdateRequest = UPDATE_NONE;
2640                 listener = mListener;
2641             }
2642 
2643             if (listener != null) {
2644                 try {
2645                     listener.onCallHeld(ImsCall.this);
2646                 } catch (Throwable t) {
2647                     loge("callSessionHeld :: ", t);
2648                 }
2649             }
2650         }
2651 
2652         @Override
callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2653         public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2654             loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo);
2655 
2656             if (isTransientConferenceSession(session)) {
2657                 // We should not get this callback for a transient session.
2658                 logi("callSessionHoldFailed :: not supported for transient conference session=" +
2659                         session);
2660                 return;
2661             }
2662 
2663             logi("callSessionHoldFailed :: session=" + session +
2664                     ", reasonInfo=" + reasonInfo);
2665 
2666             synchronized (mLockObj) {
2667                 mHold = false;
2668             }
2669 
2670             boolean isHoldForMerge = false;
2671             ImsCall.Listener listener;
2672 
2673             synchronized(ImsCall.this) {
2674                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2675                     isHoldForMerge = true;
2676                 }
2677 
2678                 mUpdateRequest = UPDATE_NONE;
2679                 listener = mListener;
2680             }
2681 
2682             if (listener != null) {
2683                 try {
2684                     listener.onCallHoldFailed(ImsCall.this, reasonInfo);
2685                 } catch (Throwable t) {
2686                     loge("callSessionHoldFailed :: ", t);
2687                 }
2688             }
2689         }
2690 
2691         /**
2692          * Indicates that an {@link ImsCallSession} has been remotely held.  This can be due to the
2693          * remote party holding the current call, or swapping between calls.
2694          * @param session the session which was held.
2695          * @param profile the profile for the held call.
2696          */
2697         @Override
callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile)2698         public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
2699             logi("callSessionHoldReceived :: session=" + session + "profile=" + profile);
2700 
2701             if (isTransientConferenceSession(session)) {
2702                 // We should not get this callback for a transient session.
2703                 logi("callSessionHoldReceived :: not supported for transient conference session=" +
2704                         session);
2705                 return;
2706             }
2707 
2708             ImsCall.Listener listener;
2709 
2710             synchronized(ImsCall.this) {
2711                 listener = mListener;
2712                 setCallProfile(profile);
2713             }
2714 
2715             if (listener != null) {
2716                 try {
2717                     listener.onCallHoldReceived(ImsCall.this);
2718                 } catch (Throwable t) {
2719                     loge("callSessionHoldReceived :: ", t);
2720                 }
2721             }
2722         }
2723 
2724         /**
2725          * Indicates that an {@link ImsCallSession} has been remotely resumed.  This can be due to
2726          * the remote party un-holding the current call, or swapping back to this call.
2727          * @param session the session which was resumed.
2728          * @param profile the profile for the held call.
2729          */
2730         @Override
callSessionResumed(ImsCallSession session, ImsCallProfile profile)2731         public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
2732             logi("callSessionResumed :: session=" + session + "profile=" + profile);
2733 
2734             if (isTransientConferenceSession(session)) {
2735                 logi("callSessionResumed :: not supported for transient conference session=" +
2736                         session);
2737                 return;
2738             }
2739 
2740             // If this call was pending a merge, it is not anymore. This is the case when we
2741             // are merging in a new call into an existing conference.
2742             setCallSessionMergePending(false);
2743 
2744             // TOOD: When we are merging a new call into an existing conference we are waiting
2745             // for 2 triggers to let us know that the conference has been established, the first
2746             // is a termination for the new calls (since it is added to the conference) the second
2747             // would be a resume on the existing conference.  If the resume comes first, then
2748             // we will make the onCallResumed() callback and its unclear how this will behave if
2749             // the termination has not come yet.
2750 
2751             ImsCall.Listener listener;
2752             synchronized(ImsCall.this) {
2753                 listener = mListener;
2754                 setCallProfile(profile);
2755                 mUpdateRequest = UPDATE_NONE;
2756                 mHold = false;
2757             }
2758 
2759             if (listener != null) {
2760                 try {
2761                     listener.onCallResumed(ImsCall.this);
2762                 } catch (Throwable t) {
2763                     loge("callSessionResumed :: ", t);
2764                 }
2765             }
2766         }
2767 
2768         @Override
callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2769         public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2770             loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2771 
2772             if (isTransientConferenceSession(session)) {
2773                 logi("callSessionResumeFailed :: not supported for transient conference session=" +
2774                         session);
2775                 return;
2776             }
2777 
2778             synchronized(mLockObj) {
2779                 mHold = true;
2780             }
2781 
2782             ImsCall.Listener listener;
2783 
2784             synchronized(ImsCall.this) {
2785                 listener = mListener;
2786                 mUpdateRequest = UPDATE_NONE;
2787             }
2788 
2789             if (listener != null) {
2790                 try {
2791                     listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2792                 } catch (Throwable t) {
2793                     loge("callSessionResumeFailed :: ", t);
2794                 }
2795             }
2796         }
2797 
2798         @Override
callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile)2799         public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
2800             logi("callSessionResumeReceived :: session=" + session + "profile=" + profile);
2801 
2802             if (isTransientConferenceSession(session)) {
2803                 logi("callSessionResumeReceived :: not supported for transient conference session=" +
2804                         session);
2805                 return;
2806             }
2807 
2808             ImsCall.Listener listener;
2809 
2810             synchronized(ImsCall.this) {
2811                 listener = mListener;
2812                 setCallProfile(profile);
2813             }
2814 
2815             if (listener != null) {
2816                 try {
2817                     listener.onCallResumeReceived(ImsCall.this);
2818                 } catch (Throwable t) {
2819                     loge("callSessionResumeReceived :: ", t);
2820                 }
2821             }
2822         }
2823 
2824         @Override
callSessionMergeStarted(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)2825         public void callSessionMergeStarted(ImsCallSession session,
2826                 ImsCallSession newSession, ImsCallProfile profile) {
2827             logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession +
2828                     ", profile=" + profile);
2829 
2830             return;
2831         }
2832 
2833         /**
2834          * We received a callback from ImsCallSession that merge completed.
2835          * @param newSession - this session can have 2 values based on the below scenarios
2836          *
2837 	 * Conference Scenarios :
2838          * Case 1 - 3 way success case
2839          * Case 2 - 3 way success case but held call fails to merge
2840          * Case 3 - 3 way success case but active call fails to merge
2841          * case 4 - 4 way success case, where merge is initiated on the foreground single-party
2842          *          call and the conference (mergeHost) is the background call.
2843          * case 5 - 4 way success case, where merge is initiated on the foreground conference
2844          *          call (mergeHost) and the single party call is in the background.
2845          *
2846          * Conference Result:
2847          * session : new session after conference
2848          * newSession = new session for case 1, 2, 3.
2849          *              Should be considered as mTransientConferencession
2850          * newSession = Active conference session for case 5 will be null
2851          *              mergehost was foreground call
2852          *              mTransientConferencession will be null
2853          * newSession = Active conference session for case 4 will be null
2854          *              mergeHost was background call
2855          *              mTransientConferencession will be null
2856          */
2857         @Override
callSessionMergeComplete(ImsCallSession newSession)2858         public void callSessionMergeComplete(ImsCallSession newSession) {
2859             logi("callSessionMergeComplete :: newSession =" + newSession);
2860             if (!isMergeHost()) {
2861                 // Handles case 4
2862                 mMergeHost.processMergeComplete();
2863             } else {
2864                 // Handles case 1, 2, 3
2865                 if (newSession != null) {
2866                     mTransientConferenceSession = newSession;
2867                 }
2868                 // Handles case 5
2869                 processMergeComplete();
2870             }
2871         }
2872 
2873         @Override
callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2874         public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2875             loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2876 
2877             // Its possible that there could be threading issues with the other thread handling
2878             // the other call. This could affect our state.
2879             synchronized (ImsCall.this) {
2880                 // Let's tell our parent ImsCall that the merge has failed and we need to clean
2881                 // up any temporary, transient state.  Note this only gets called for an initial
2882                 // conference.  If a merge into an existing conference fails, the two sessions will
2883                 // just go back to their original state (ACTIVE or HELD).
2884                 if (isMergeHost()) {
2885                     processMergeFailed(reasonInfo);
2886                 } else if (mMergeHost != null) {
2887                     mMergeHost.processMergeFailed(reasonInfo);
2888                 } else {
2889                     loge("callSessionMergeFailed :: No merge host for this conference!");
2890                 }
2891             }
2892         }
2893 
2894         @Override
callSessionUpdated(ImsCallSession session, ImsCallProfile profile)2895         public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2896             logi("callSessionUpdated :: session=" + session + " profile=" + profile);
2897 
2898             if (isTransientConferenceSession(session)) {
2899                 logi("callSessionUpdated :: not supported for transient conference session=" +
2900                         session);
2901                 return;
2902             }
2903 
2904             ImsCall.Listener listener;
2905 
2906             synchronized(ImsCall.this) {
2907                 listener = mListener;
2908                 setCallProfile(profile);
2909             }
2910 
2911             if (listener != null) {
2912                 try {
2913                     listener.onCallUpdated(ImsCall.this);
2914                 } catch (Throwable t) {
2915                     loge("callSessionUpdated :: ", t);
2916                 }
2917             }
2918         }
2919 
2920         @Override
callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2921         public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2922             loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2923 
2924             if (isTransientConferenceSession(session)) {
2925                 logi("callSessionUpdateFailed :: not supported for transient conference session=" +
2926                         session);
2927                 return;
2928             }
2929 
2930             ImsCall.Listener listener;
2931 
2932             synchronized(ImsCall.this) {
2933                 listener = mListener;
2934                 mUpdateRequest = UPDATE_NONE;
2935             }
2936 
2937             if (listener != null) {
2938                 try {
2939                     listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2940                 } catch (Throwable t) {
2941                     loge("callSessionUpdateFailed :: ", t);
2942                 }
2943             }
2944         }
2945 
2946         @Override
callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile)2947         public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2948             logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile);
2949 
2950             if (isTransientConferenceSession(session)) {
2951                 logi("callSessionUpdateReceived :: not supported for transient conference " +
2952                         "session=" + session);
2953                 return;
2954             }
2955 
2956             ImsCall.Listener listener;
2957 
2958             synchronized(ImsCall.this) {
2959                 listener = mListener;
2960                 mProposedCallProfile = profile;
2961                 mUpdateRequest = UPDATE_UNSPECIFIED;
2962             }
2963 
2964             if (listener != null) {
2965                 try {
2966                     listener.onCallUpdateReceived(ImsCall.this);
2967                 } catch (Throwable t) {
2968                     loge("callSessionUpdateReceived :: ", t);
2969                 }
2970             }
2971         }
2972 
2973         @Override
callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)2974         public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2975                 ImsCallProfile profile) {
2976             logi("callSessionConferenceExtended :: session=" + session  + " newSession=" +
2977                     newSession + ", profile=" + profile);
2978 
2979             if (isTransientConferenceSession(session)) {
2980                 logi("callSessionConferenceExtended :: not supported for transient conference " +
2981                         "session=" + session);
2982                 return;
2983             }
2984 
2985             ImsCall newCall = createNewCall(newSession, profile);
2986 
2987             if (newCall == null) {
2988                 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2989                 return;
2990             }
2991 
2992             ImsCall.Listener listener;
2993 
2994             synchronized(ImsCall.this) {
2995                 listener = mListener;
2996                 mUpdateRequest = UPDATE_NONE;
2997             }
2998 
2999             if (listener != null) {
3000                 try {
3001                     listener.onCallConferenceExtended(ImsCall.this, newCall);
3002                 } catch (Throwable t) {
3003                     loge("callSessionConferenceExtended :: ", t);
3004                 }
3005             }
3006         }
3007 
3008         @Override
callSessionConferenceExtendFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3009         public void callSessionConferenceExtendFailed(ImsCallSession session,
3010                 ImsReasonInfo reasonInfo) {
3011             loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo);
3012 
3013             if (isTransientConferenceSession(session)) {
3014                 logi("callSessionConferenceExtendFailed :: not supported for transient " +
3015                         "conference session=" + session);
3016                 return;
3017             }
3018 
3019             ImsCall.Listener listener;
3020 
3021             synchronized(ImsCall.this) {
3022                 listener = mListener;
3023                 mUpdateRequest = UPDATE_NONE;
3024             }
3025 
3026             if (listener != null) {
3027                 try {
3028                     listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
3029                 } catch (Throwable t) {
3030                     loge("callSessionConferenceExtendFailed :: ", t);
3031                 }
3032             }
3033         }
3034 
3035         @Override
callSessionConferenceExtendReceived(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)3036         public void callSessionConferenceExtendReceived(ImsCallSession session,
3037                 ImsCallSession newSession, ImsCallProfile profile) {
3038             logi("callSessionConferenceExtendReceived :: newSession=" + newSession +
3039                     ", profile=" + profile);
3040 
3041             if (isTransientConferenceSession(session)) {
3042                 logi("callSessionConferenceExtendReceived :: not supported for transient " +
3043                         "conference session" + session);
3044                 return;
3045             }
3046 
3047             ImsCall newCall = createNewCall(newSession, profile);
3048 
3049             if (newCall == null) {
3050                 // Should all the calls be terminated...???
3051                 return;
3052             }
3053 
3054             ImsCall.Listener listener;
3055 
3056             synchronized(ImsCall.this) {
3057                 listener = mListener;
3058             }
3059 
3060             if (listener != null) {
3061                 try {
3062                     listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
3063                 } catch (Throwable t) {
3064                     loge("callSessionConferenceExtendReceived :: ", t);
3065                 }
3066             }
3067         }
3068 
3069         @Override
callSessionInviteParticipantsRequestDelivered(ImsCallSession session)3070         public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
3071             logi("callSessionInviteParticipantsRequestDelivered ::");
3072 
3073             if (isTransientConferenceSession(session)) {
3074                 logi("callSessionInviteParticipantsRequestDelivered :: not supported for " +
3075                         "conference session=" + session);
3076                 return;
3077             }
3078 
3079             ImsCall.Listener listener;
3080 
3081             synchronized(ImsCall.this) {
3082                 listener = mListener;
3083             }
3084 
3085             mIsConferenceHost = true;
3086 
3087             if (listener != null) {
3088                 try {
3089                     listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
3090                 } catch (Throwable t) {
3091                     loge("callSessionInviteParticipantsRequestDelivered :: ", t);
3092                 }
3093             }
3094         }
3095 
3096         @Override
callSessionInviteParticipantsRequestFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3097         public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
3098                 ImsReasonInfo reasonInfo) {
3099             loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
3100 
3101             if (isTransientConferenceSession(session)) {
3102                 logi("callSessionInviteParticipantsRequestFailed :: not supported for " +
3103                         "conference session=" + session);
3104                 return;
3105             }
3106 
3107             ImsCall.Listener listener;
3108 
3109             synchronized(ImsCall.this) {
3110                 listener = mListener;
3111             }
3112 
3113             if (listener != null) {
3114                 try {
3115                     listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
3116                 } catch (Throwable t) {
3117                     loge("callSessionInviteParticipantsRequestFailed :: ", t);
3118                 }
3119             }
3120         }
3121 
3122         @Override
callSessionRemoveParticipantsRequestDelivered(ImsCallSession session)3123         public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
3124             logi("callSessionRemoveParticipantsRequestDelivered ::");
3125 
3126             if (isTransientConferenceSession(session)) {
3127                 logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
3128                         "conference session=" + session);
3129                 return;
3130             }
3131 
3132             ImsCall.Listener listener;
3133 
3134             synchronized(ImsCall.this) {
3135                 listener = mListener;
3136             }
3137 
3138             if (listener != null) {
3139                 try {
3140                     listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
3141                 } catch (Throwable t) {
3142                     loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
3143                 }
3144             }
3145         }
3146 
3147         @Override
callSessionRemoveParticipantsRequestFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3148         public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
3149                 ImsReasonInfo reasonInfo) {
3150             loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
3151 
3152             if (isTransientConferenceSession(session)) {
3153                 logi("callSessionRemoveParticipantsRequestFailed :: not supported for " +
3154                         "conference session=" + session);
3155                 return;
3156             }
3157 
3158             ImsCall.Listener listener;
3159 
3160             synchronized(ImsCall.this) {
3161                 listener = mListener;
3162             }
3163 
3164             if (listener != null) {
3165                 try {
3166                     listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
3167                 } catch (Throwable t) {
3168                     loge("callSessionRemoveParticipantsRequestFailed :: ", t);
3169                 }
3170             }
3171         }
3172 
3173         @Override
callSessionConferenceStateUpdated(ImsCallSession session, ImsConferenceState state)3174         public void callSessionConferenceStateUpdated(ImsCallSession session,
3175                 ImsConferenceState state) {
3176             logi("callSessionConferenceStateUpdated :: state=" + state);
3177             conferenceStateUpdated(state);
3178         }
3179 
3180         @Override
callSessionUssdMessageReceived(ImsCallSession session, int mode, String ussdMessage)3181         public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
3182                 String ussdMessage) {
3183             logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
3184                     ussdMessage);
3185 
3186             if (isTransientConferenceSession(session)) {
3187                 logi("callSessionUssdMessageReceived :: not supported for transient " +
3188                         "conference session=" + session);
3189                 return;
3190             }
3191 
3192             ImsCall.Listener listener;
3193 
3194             synchronized(ImsCall.this) {
3195                 listener = mListener;
3196             }
3197 
3198             if (listener != null) {
3199                 try {
3200                     listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
3201                 } catch (Throwable t) {
3202                     loge("callSessionUssdMessageReceived :: ", t);
3203                 }
3204             }
3205         }
3206 
3207         @Override
callSessionTtyModeReceived(ImsCallSession session, int mode)3208         public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
3209             logi("callSessionTtyModeReceived :: mode=" + mode);
3210 
3211             ImsCall.Listener listener;
3212 
3213             synchronized(ImsCall.this) {
3214                 listener = mListener;
3215             }
3216 
3217             if (listener != null) {
3218                 try {
3219                     listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
3220                 } catch (Throwable t) {
3221                     loge("callSessionTtyModeReceived :: ", t);
3222                 }
3223             }
3224         }
3225 
3226         /**
3227          * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
3228          *
3229          * @param session The call session.
3230          * @param isMultiParty {@code true} if the session became multiparty, {@code false}
3231          *      otherwise.
3232          */
3233         @Override
callSessionMultipartyStateChanged(ImsCallSession session, boolean isMultiParty)3234         public void callSessionMultipartyStateChanged(ImsCallSession session,
3235                 boolean isMultiParty) {
3236             if (VDBG) {
3237                 logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
3238                         : "N"));
3239             }
3240 
3241             ImsCall.Listener listener;
3242 
3243             synchronized(ImsCall.this) {
3244                 listener = mListener;
3245             }
3246 
3247             if (listener != null) {
3248                 try {
3249                     listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
3250                 } catch (Throwable t) {
3251                     loge("callSessionMultipartyStateChanged :: ", t);
3252                 }
3253             }
3254         }
3255 
callSessionHandover(ImsCallSession session, int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo)3256         public void callSessionHandover(ImsCallSession session, int srcNetworkType,
3257             int targetNetworkType, ImsReasonInfo reasonInfo) {
3258             logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
3259                     srcNetworkType + ", targetAccessTech=" + targetNetworkType + ", reasonInfo=" +
3260                 reasonInfo);
3261 
3262             ImsCall.Listener listener;
3263 
3264             synchronized(ImsCall.this) {
3265                 listener = mListener;
3266             }
3267 
3268             if (listener != null) {
3269                 try {
3270                     listener.onCallHandover(ImsCall.this,
3271                             ServiceState.networkTypeToRilRadioTechnology(srcNetworkType),
3272                             ServiceState.networkTypeToRilRadioTechnology(targetNetworkType),
3273                             reasonInfo);
3274                 } catch (Throwable t) {
3275                     loge("callSessionHandover :: ", t);
3276                 }
3277             }
3278         }
3279 
3280         @Override
callSessionHandoverFailed(ImsCallSession session, int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo)3281         public void callSessionHandoverFailed(ImsCallSession session, int srcNetworkType,
3282             int targetNetworkType, ImsReasonInfo reasonInfo) {
3283             loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" +
3284                     srcNetworkType + ", targetAccessTech=" + targetNetworkType + ", reasonInfo=" +
3285                 reasonInfo);
3286 
3287             ImsCall.Listener listener;
3288 
3289             synchronized(ImsCall.this) {
3290                 listener = mListener;
3291             }
3292 
3293             if (listener != null) {
3294                 try {
3295                     listener.onCallHandoverFailed(ImsCall.this,
3296                             ServiceState.networkTypeToRilRadioTechnology(srcNetworkType),
3297                             ServiceState.networkTypeToRilRadioTechnology(targetNetworkType),
3298                             reasonInfo);
3299                 } catch (Throwable t) {
3300                     loge("callSessionHandoverFailed :: ", t);
3301                 }
3302             }
3303         }
3304 
3305         @Override
callSessionSuppServiceReceived(ImsCallSession session, ImsSuppServiceNotification suppServiceInfo )3306         public void callSessionSuppServiceReceived(ImsCallSession session,
3307                 ImsSuppServiceNotification suppServiceInfo ) {
3308             if (isTransientConferenceSession(session)) {
3309                 logi("callSessionSuppServiceReceived :: not supported for transient conference"
3310                         + " session=" + session);
3311                 return;
3312             }
3313 
3314             logi("callSessionSuppServiceReceived :: session=" + session +
3315                      ", suppServiceInfo" + suppServiceInfo);
3316 
3317             ImsCall.Listener listener;
3318 
3319             synchronized(ImsCall.this) {
3320                 listener = mListener;
3321             }
3322 
3323             if (listener != null) {
3324                 try {
3325                     listener.onCallSuppServiceReceived(ImsCall.this, suppServiceInfo);
3326                 } catch (Throwable t) {
3327                     loge("callSessionSuppServiceReceived :: ", t);
3328                 }
3329             }
3330         }
3331 
3332         @Override
callSessionRttModifyRequestReceived(ImsCallSession session, ImsCallProfile callProfile)3333         public void callSessionRttModifyRequestReceived(ImsCallSession session,
3334                 ImsCallProfile callProfile) {
3335             ImsCall.Listener listener;
3336             logi("callSessionRttModifyRequestReceived");
3337 
3338             synchronized(ImsCall.this) {
3339                 listener = mListener;
3340             }
3341 
3342             if (!callProfile.mMediaProfile.isRttCall()) {
3343                 logi("callSessionRttModifyRequestReceived:: ignoring request, requested profile " +
3344                         "is not RTT.");
3345                 return;
3346             }
3347 
3348             if (listener != null) {
3349                 try {
3350                     listener.onRttModifyRequestReceived(ImsCall.this);
3351                 } catch (Throwable t) {
3352                     loge("callSessionRttModifyRequestReceived:: ", t);
3353                 }
3354             }
3355         }
3356 
3357         @Override
callSessionRttModifyResponseReceived(int status)3358         public void callSessionRttModifyResponseReceived(int status) {
3359             ImsCall.Listener listener;
3360 
3361             logi("callSessionRttModifyResponseReceived: " + status);
3362             synchronized(ImsCall.this) {
3363                 listener = mListener;
3364             }
3365 
3366             if (listener != null) {
3367                 try {
3368                     listener.onRttModifyResponseReceived(ImsCall.this, status);
3369                 } catch (Throwable t) {
3370                     loge("callSessionRttModifyResponseReceived:: ", t);
3371                 }
3372             }
3373         }
3374 
3375         @Override
callSessionRttMessageReceived(String rttMessage)3376         public void callSessionRttMessageReceived(String rttMessage) {
3377             ImsCall.Listener listener;
3378 
3379             synchronized(ImsCall.this) {
3380                 listener = mListener;
3381             }
3382 
3383             if (listener != null) {
3384                 try {
3385                     listener.onRttMessageReceived(ImsCall.this, rttMessage);
3386                 } catch (Throwable t) {
3387                     loge("callSessionRttMessageReceived:: ", t);
3388                 }
3389             }
3390         }
3391 
3392         @Override
callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile)3393         public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
3394             ImsCall.Listener listener;
3395 
3396             synchronized(ImsCall.this) {
3397                 listener = mListener;
3398             }
3399 
3400             if (listener != null) {
3401                 try {
3402                     listener.onRttAudioIndicatorChanged(ImsCall.this, profile);
3403                 } catch (Throwable t) {
3404                     loge("callSessionRttAudioIndicatorChanged:: ", t);
3405                 }
3406             }
3407         }
3408 
3409         @Override
callSessionTransferred(ImsCallSession session)3410         public void callSessionTransferred(ImsCallSession session) {
3411             ImsCall.Listener listener;
3412 
3413             synchronized(ImsCall.this) {
3414                 listener = mListener;
3415             }
3416 
3417             if (listener != null) {
3418                 try {
3419                     listener.onCallSessionTransferred(ImsCall.this);
3420                 } catch (Throwable t) {
3421                     loge("callSessionTransferred:: ", t);
3422                 }
3423             }
3424         }
3425 
3426         @Override
callSessionTransferFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3427         public void callSessionTransferFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
3428             ImsCall.Listener listener;
3429 
3430             synchronized(ImsCall.this) {
3431                 listener = mListener;
3432             }
3433 
3434             if (listener != null) {
3435                 try {
3436                     listener.onCallSessionTransferFailed(ImsCall.this, reasonInfo);
3437                 } catch (Throwable t) {
3438                     loge("callSessionTransferFailed:: ", t);
3439                 }
3440             }
3441         }
3442 
3443         @Override
callSessionDtmfReceived(char digit)3444         public void callSessionDtmfReceived(char digit) {
3445             ImsCall.Listener listener;
3446 
3447             synchronized(ImsCall.this) {
3448                 listener = mListener;
3449             }
3450 
3451             if (listener != null) {
3452                 try {
3453                     listener.onCallSessionDtmfReceived(ImsCall.this, digit);
3454                 } catch (Throwable t) {
3455                     loge("callSessionDtmfReceived:: ", t);
3456                 }
3457             }
3458         }
3459 
3460         @Override
callQualityChanged(CallQuality callQuality)3461         public void callQualityChanged(CallQuality callQuality) {
3462             ImsCall.Listener listener;
3463 
3464             synchronized (ImsCall.this) {
3465                 listener = mListener;
3466             }
3467 
3468             if (listener != null) {
3469                 try {
3470                     listener.onCallQualityChanged(ImsCall.this, callQuality);
3471                 } catch (Throwable t) {
3472                     loge("callQualityChanged:: ", t);
3473                 }
3474             }
3475         }
3476 
3477         @Override
callSessionRtpHeaderExtensionsReceived( @onNull Set<RtpHeaderExtension> extensions)3478         public void callSessionRtpHeaderExtensionsReceived(
3479                 @NonNull Set<RtpHeaderExtension> extensions) {
3480             ImsCall.Listener listener;
3481 
3482             synchronized (ImsCall.this) {
3483                 listener = mListener;
3484             }
3485 
3486             if (listener != null) {
3487                 try {
3488                     listener.onCallSessionRtpHeaderExtensionsReceived(ImsCall.this, extensions);
3489                 } catch (Throwable t) {
3490                     loge("callSessionRtpHeaderExtensionsReceived:: ", t);
3491                 }
3492             }
3493         }
3494     }
3495 
3496     /**
3497      * Report a new conference state to the current {@link ImsCall} and inform listeners of the
3498      * change.  Marked as {@code VisibleForTesting} so that the
3499      * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
3500      * event package into a regular ongoing IMS call.
3501      *
3502      * @param state The {@link ImsConferenceState}.
3503      */
3504     @VisibleForTesting
conferenceStateUpdated(ImsConferenceState state)3505     public void conferenceStateUpdated(ImsConferenceState state) {
3506         Listener listener;
3507 
3508         synchronized(this) {
3509             notifyConferenceStateUpdated(state);
3510             listener = mListener;
3511         }
3512 
3513         if (listener != null) {
3514             try {
3515                 listener.onCallConferenceStateUpdated(this, state);
3516             } catch (Throwable t) {
3517                 loge("callSessionConferenceStateUpdated :: ", t);
3518             }
3519         }
3520     }
3521 
3522     /**
3523      * Provides a human-readable string representation of an update request.
3524      *
3525      * @param updateRequest The update request.
3526      * @return The string representation.
3527      */
updateRequestToString(int updateRequest)3528     private String updateRequestToString(int updateRequest) {
3529         switch (updateRequest) {
3530             case UPDATE_NONE:
3531                 return "NONE";
3532             case UPDATE_HOLD:
3533                 return "HOLD";
3534             case UPDATE_HOLD_MERGE:
3535                 return "HOLD_MERGE";
3536             case UPDATE_RESUME:
3537                 return "RESUME";
3538             case UPDATE_MERGE:
3539                 return "MERGE";
3540             case UPDATE_EXTEND_TO_CONFERENCE:
3541                 return "EXTEND_TO_CONFERENCE";
3542             case UPDATE_UNSPECIFIED:
3543                 return "UNSPECIFIED";
3544             default:
3545                 return "UNKNOWN";
3546         }
3547     }
3548 
3549     /**
3550      * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
3551      * severed at the same time.
3552      */
clearMergeInfo()3553     private void clearMergeInfo() {
3554         if (CONF_DBG) {
3555             logi("clearMergeInfo :: clearing all merge info");
3556         }
3557 
3558         // First clear out the merge partner then clear ourselves out.
3559         if (mMergeHost != null) {
3560             mMergeHost.mMergePeer = null;
3561             mMergeHost.mUpdateRequest = UPDATE_NONE;
3562             mMergeHost.mCallSessionMergePending = false;
3563         }
3564         if (mMergePeer != null) {
3565             mMergePeer.mMergeHost = null;
3566             mMergePeer.mUpdateRequest = UPDATE_NONE;
3567             mMergePeer.mCallSessionMergePending = false;
3568         }
3569         mMergeHost = null;
3570         mMergePeer = null;
3571         mUpdateRequest = UPDATE_NONE;
3572         mCallSessionMergePending = false;
3573     }
3574 
3575     /**
3576      * Sets the merge peer for the current call.  The merge peer is the background call that will be
3577      * merged into this call.  On the merge peer, sets the merge host to be this call.
3578      *
3579      * @param mergePeer The peer call to be merged into this one.
3580      */
setMergePeer(ImsCall mergePeer)3581     private void setMergePeer(ImsCall mergePeer) {
3582         mMergePeer = mergePeer;
3583         mMergeHost = null;
3584 
3585         mergePeer.mMergeHost = ImsCall.this;
3586         mergePeer.mMergePeer = null;
3587     }
3588 
3589     /**
3590      * Sets the merge hody for the current call.  The merge host is the foreground call this call
3591      * will be merged into.  On the merge host, sets the merge peer to be this call.
3592      *
3593      * @param mergeHost The merge host this call will be merged into.
3594      */
setMergeHost(ImsCall mergeHost)3595     public void setMergeHost(ImsCall mergeHost) {
3596         mMergeHost = mergeHost;
3597         mMergePeer = null;
3598 
3599         mergeHost.mMergeHost = null;
3600         mergeHost.mMergePeer = ImsCall.this;
3601     }
3602 
3603     /**
3604      * Determines if the current call is in the process of merging with another call or conference.
3605      *
3606      * @return {@code true} if in the process of merging.
3607      */
isMerging()3608     private boolean isMerging() {
3609         return mMergePeer != null || mMergeHost != null;
3610     }
3611 
3612     /**
3613      * Determines if the current call is the host of the merge.
3614      *
3615      * @return {@code true} if the call is the merge host.
3616      */
isMergeHost()3617     private boolean isMergeHost() {
3618         return mMergePeer != null && mMergeHost == null;
3619     }
3620 
3621     /**
3622      * Determines if the current call is the peer of the merge.
3623      *
3624      * @return {@code true} if the call is the merge peer.
3625      */
isMergePeer()3626     private boolean isMergePeer() {
3627         return mMergePeer == null && mMergeHost != null;
3628     }
3629 
3630     /**
3631      * Determines if the call session is pending merge into a conference or not.
3632      *
3633      * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
3634      */
isCallSessionMergePending()3635     public boolean isCallSessionMergePending() {
3636         return mCallSessionMergePending;
3637     }
3638 
3639     /**
3640      * Sets flag indicating whether the call session is pending merge into a conference or not.
3641      *
3642      * @param callSessionMergePending {@code true} if a merge into the conference is pending,
3643      *      {@code false} otherwise.
3644      */
setCallSessionMergePending(boolean callSessionMergePending)3645     private void setCallSessionMergePending(boolean callSessionMergePending) {
3646         mCallSessionMergePending = callSessionMergePending;
3647     }
3648 
3649     /**
3650      * Determines if there is a conference merge in process.  If there is a merge in process,
3651      * determines if both the merge host and peer sessions have completed the merge process.  This
3652      * means that we have received terminate or hold signals for the sessions, indicating that they
3653      * are no longer in the process of being merged into the conference.
3654      * <p>
3655      * The sessions are considered to have merged if: both calls still have merge peer/host
3656      * relationships configured,  both sessions are not waiting to be merged into the conference,
3657      * and the transient conference session is alive in the case of an initial conference.
3658      *
3659      * @return {@code true} where the host and peer sessions have finished merging into the
3660      *      conference, {@code false} if the merge has not yet completed, and {@code false} if there
3661      *      is no conference merge in progress.
3662      */
shouldProcessConferenceResult()3663     private boolean shouldProcessConferenceResult() {
3664         boolean areMergeTriggersDone = false;
3665 
3666         synchronized (ImsCall.this) {
3667             // if there is a merge going on, then the merge host/peer relationships should have been
3668             // set up.  This works for both the initial conference or merging a call into an
3669             // existing conference.
3670             if (!isMergeHost() && !isMergePeer()) {
3671                 if (CONF_DBG) {
3672                     loge("shouldProcessConferenceResult :: no merge in progress");
3673                 }
3674                 return false;
3675             }
3676 
3677             // There is a merge in progress, so check the sessions to ensure:
3678             // 1. Both calls have completed being merged (or failing to merge) into the conference.
3679             // 2. The transient conference session is alive.
3680             if (isMergeHost()) {
3681                 if (CONF_DBG) {
3682                     logi("shouldProcessConferenceResult :: We are a merge host");
3683                     logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer);
3684                 }
3685                 areMergeTriggersDone = !isCallSessionMergePending() &&
3686                         !mMergePeer.isCallSessionMergePending();
3687                 if (!isMultiparty()) {
3688                     // Only check the transient session when there is no existing conference
3689                     areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession);
3690                 }
3691             } else if (isMergePeer()) {
3692                 if (CONF_DBG) {
3693                     logi("shouldProcessConferenceResult :: We are a merge peer");
3694                     logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost);
3695                 }
3696                 areMergeTriggersDone = !isCallSessionMergePending() &&
3697                         !mMergeHost.isCallSessionMergePending();
3698                 if (!mMergeHost.isMultiparty()) {
3699                     // Only check the transient session when there is no existing conference
3700                     areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession);
3701                 } else {
3702                     // This else block is a special case for Verizon to handle these steps
3703                     // 1. Establish a conference call.
3704                     // 2. Add a new call (conference in in BG)
3705                     // 3. Swap (conference active on FG)
3706                     // 4. Merge
3707                     // What happens here is that the BG call gets a terminated callback
3708                     // because it was added to the conference. I've seen where
3709                     // the FG gets no callback at all because its already active.
3710                     // So if we continue to wait for it to set its isCallSessionMerging
3711                     // flag to false...we'll be waiting forever.
3712                     areMergeTriggersDone = !isCallSessionMergePending();
3713                 }
3714             } else {
3715                 // Realistically this shouldn't happen, but best to be safe.
3716                 loge("shouldProcessConferenceResult : merge in progress but call is neither" +
3717                         " host nor peer.");
3718             }
3719             if (CONF_DBG) {
3720                 logi("shouldProcessConferenceResult :: returning:" +
3721                         (areMergeTriggersDone ? "true" : "false"));
3722             }
3723         }
3724         return areMergeTriggersDone;
3725     }
3726 
3727     /**
3728      * Provides a string representation of the {@link ImsCall}.  Primarily intended for use in log
3729      * statements.
3730      *
3731      * @return String representation of call.
3732      */
3733     @Override
toString()3734     public String toString() {
3735         StringBuilder sb = new StringBuilder();
3736         sb.append("[ImsCall objId:");
3737         sb.append(System.identityHashCode(this));
3738         sb.append(" onHold:");
3739         sb.append(isOnHold() ? "Y" : "N");
3740         sb.append(" mute:");
3741         sb.append(isMuted() ? "Y" : "N");
3742         ImsCallProfile imsCallProfile = mCallProfile;
3743         if (imsCallProfile != null) {
3744             sb.append(" mCallProfile:" + imsCallProfile);
3745             sb.append(" networkType:");
3746             sb.append(getNetworkType());
3747         }
3748         sb.append(" updateRequest:");
3749         sb.append(updateRequestToString(mUpdateRequest));
3750         sb.append(" merging:");
3751         sb.append(isMerging() ? "Y" : "N");
3752         if (isMerging()) {
3753             if (isMergePeer()) {
3754                 sb.append("P");
3755             } else {
3756                 sb.append("H");
3757             }
3758         }
3759         sb.append(" merge action pending:");
3760         sb.append(isCallSessionMergePending() ? "Y" : "N");
3761         sb.append(" merged:");
3762         sb.append(isMerged() ? "Y" : "N");
3763         sb.append(" multiParty:");
3764         sb.append(isMultiparty() ? "Y" : "N");
3765         sb.append(" confHost:");
3766         sb.append(isConferenceHost() ? "Y" : "N");
3767         sb.append(" buried term:");
3768         sb.append(mSessionEndDuringMerge ? "Y" : "N");
3769         sb.append(" isVideo: ");
3770         sb.append(isVideoCall() ? "Y" : "N");
3771         sb.append(" wasVideo: ");
3772         sb.append(mWasVideoCall ? "Y" : "N");
3773         sb.append(" isWifi: ");
3774         sb.append(isWifiCall() ? "Y" : "N");
3775         sb.append(" session:");
3776         sb.append(mSession);
3777         sb.append(" transientSession:");
3778         sb.append(mTransientConferenceSession);
3779         sb.append("]");
3780         return sb.toString();
3781     }
3782 
throwImsException(Throwable t, int code)3783     private void throwImsException(Throwable t, int code) throws ImsException {
3784         if (t instanceof ImsException) {
3785             throw (ImsException) t;
3786         } else {
3787             throw new ImsException(String.valueOf(code), t, code);
3788         }
3789     }
3790 
3791     /**
3792      * Append the ImsCall information to the provided string. Usefull for as a logging helper.
3793      * @param s The original string
3794      * @return The original string with {@code ImsCall} information appended to it.
3795      */
appendImsCallInfoToString(String s)3796     private String appendImsCallInfoToString(String s) {
3797         StringBuilder sb = new StringBuilder();
3798         sb.append(s);
3799         sb.append(" ImsCall=");
3800         sb.append(ImsCall.this);
3801         return sb.toString();
3802     }
3803 
3804     /**
3805      * Updates {@link #mWasVideoCall} based on the current {@link ImsCallProfile} for the call.
3806      *
3807      * @param profile The current {@link ImsCallProfile} for the call.
3808      */
trackVideoStateHistory(ImsCallProfile profile)3809     private void trackVideoStateHistory(ImsCallProfile profile) {
3810         mWasVideoCall = mWasVideoCall || ( profile != null && profile.isVideoCall());
3811     }
3812 
3813     /**
3814      * @return {@code true} if this call was a video call at some point in its life span,
3815      *      {@code false} otherwise.
3816      */
wasVideoCall()3817     public boolean wasVideoCall() {
3818         return mWasVideoCall;
3819     }
3820 
3821     /**
3822      * @return {@code true} if this call is a video call, {@code false} otherwise.
3823      */
isVideoCall()3824     public boolean isVideoCall() {
3825         synchronized(mLockObj) {
3826             return mCallProfile != null && mCallProfile.isVideoCall();
3827         }
3828     }
3829 
3830     /**
3831      * Determines if the current call radio access technology is over WIFI.
3832      * Note: This depends on the RIL exposing the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra.
3833      * This method is primarily intended to be used when checking if answering an incoming audio
3834      * call should cause a wifi video call to drop (e.g.
3835      * {@link android.telephony.CarrierConfigManager#
3836      * KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is set).
3837      *
3838      * @return {@code true} if the call is over WIFI, {@code false} otherwise.
3839      */
isWifiCall()3840     public boolean isWifiCall() {
3841         synchronized(mLockObj) {
3842             if (mCallProfile == null) {
3843                 return false;
3844             }
3845             return getNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN;
3846         }
3847     }
3848 
3849     /**
3850      * Determines the network type for the {@link ImsCall}.
3851      * @return The {@link TelephonyManager} {@code NETWORK_TYPE_*} code in use.
3852      */
getNetworkType()3853     public int getNetworkType() {
3854         synchronized(mLockObj) {
3855             if (mCallProfile == null) {
3856                 return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3857             }
3858             int networkType = mCallProfile.getCallExtraInt(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE,
3859                     TelephonyManager.NETWORK_TYPE_UNKNOWN);
3860             if (networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN) {
3861                 //  Try to look at old extras to see if the ImsService is using deprecated behavior.
3862                 String oldRatType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE);
3863                 if (TextUtils.isEmpty(oldRatType)) {
3864                     oldRatType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT);
3865                 }
3866                 try {
3867                     int oldRatTypeConverted = Integer.parseInt(oldRatType);
3868                     networkType = ServiceState.rilRadioTechnologyToNetworkType(oldRatTypeConverted);
3869                 } catch (NumberFormatException e) {
3870                     networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
3871                 }
3872             }
3873             return networkType;
3874         }
3875     }
3876 
3877     /**
3878      * Determines if the current call is a cross sim call
3879      * Note: This depends on the RIL exposing the
3880      * {@link ImsCallProfile#EXTRA_IS_CROSS_SIM_CALL} extra.
3881      *
3882      * @return {@code true} if the call is Cross SIM, {@code false} otherwise.
3883      */
isCrossSimCall()3884     public boolean isCrossSimCall() {
3885         synchronized(mLockObj) {
3886             if (mCallProfile == null) {
3887                 return false;
3888             }
3889             return mCallProfile.getCallExtraBoolean(
3890                     ImsCallProfile.EXTRA_IS_CROSS_SIM_CALL,
3891                     false);
3892         }
3893     }
3894 
3895     /**
3896      * Log a string to the radio buffer at the info level.
3897      * @param s The message to log
3898      */
logi(String s)3899     private void logi(String s) {
3900         Log.i(TAG, appendImsCallInfoToString(s));
3901     }
3902 
3903     /**
3904      * Log a string to the radio buffer at the debug level.
3905      * @param s The message to log
3906      */
logd(String s)3907     private void logd(String s) {
3908         Log.d(TAG, appendImsCallInfoToString(s));
3909     }
3910 
3911     /**
3912      * Log a string to the radio buffer at the verbose level.
3913      * @param s The message to log
3914      */
logv(String s)3915     private void logv(String s) {
3916         Log.v(TAG, appendImsCallInfoToString(s));
3917     }
3918 
3919     /**
3920      * Log a string to the radio buffer at the error level.
3921      * @param s The message to log
3922      */
loge(String s)3923     private void loge(String s) {
3924         Log.e(TAG, appendImsCallInfoToString(s));
3925     }
3926 
3927     /**
3928      * Log a string to the radio buffer at the error level with a throwable
3929      * @param s The message to log
3930      * @param t The associated throwable
3931      */
loge(String s, Throwable t)3932     private void loge(String s, Throwable t) {
3933         Log.e(TAG, appendImsCallInfoToString(s), t);
3934     }
3935 }
3936