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