• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ims;
18 
19 import android.annotation.NonNull;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.Context;
22 import android.net.Uri;
23 import android.os.Build;
24 import android.os.Bundle;
25 import android.os.Message;
26 import android.os.Parcel;
27 import android.telecom.Call;
28 import com.android.ims.internal.ConferenceParticipant;
29 import android.telecom.Connection;
30 import android.telephony.CallQuality;
31 import android.telephony.ServiceState;
32 import android.telephony.TelephonyManager;
33 import android.telephony.ims.ImsCallProfile;
34 import android.telephony.ims.ImsCallSession;
35 import android.telephony.ims.ImsCallSessionListener;
36 import android.telephony.ims.ImsConferenceState;
37 import android.telephony.ims.ImsReasonInfo;
38 import android.telephony.ims.ImsStreamMediaProfile;
39 import android.telephony.ims.ImsSuppServiceNotification;
40 import android.telephony.ims.RtpHeaderExtension;
41 import android.text.TextUtils;
42 import android.util.Log;
43 
44 import com.android.ims.internal.ICall;
45 import com.android.ims.internal.ImsStreamMediaSession;
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.telephony.Rlog;
48 
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.Iterator;
52 import java.util.List;
53 import java.util.Map.Entry;
54 import java.util.Set;
55 import java.util.concurrent.atomic.AtomicInteger;
56 
57 /**
58  * Handles an IMS voice / video call over LTE. You can instantiate this class with
59  * {@link ImsManager}.
60  *
61  * @hide
62  */
63 public class ImsCall implements ICall {
64     // Mode of USSD message
65     public static final int USSD_MODE_NOTIFY = 0;
66     public static final int USSD_MODE_REQUEST = 1;
67 
68     private static final String TAG = "ImsCall";
69 
70     // This flag is meant to be used as a debugging tool to quickly see all logs
71     // regardless of the actual log level set on this component.
72     private static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
73 
74     // We will log messages guarded by these flags at the info level. If logging is required
75     // to occur at (and only at) a particular log level, please use the logd, logv and loge
76     // functions as those will not be affected by the value of FORCE_DEBUG at all.
77     // Otherwise, anything guarded by these flags will be logged at the info level since that
78     // level allows those statements ot be logged by default which supports the workflow of
79     // setting FORCE_DEBUG and knowing these logs will show up regardless of the actual log
80     // level of this component.
81     private static final boolean DBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.DEBUG);
82     private static final boolean VDBG = FORCE_DEBUG || Log.isLoggable(TAG, Log.VERBOSE);
83     // This is a special flag that is used only to highlight specific log around bringing
84     // up and tearing down conference calls. At times, these errors are transient and hard to
85     // reproduce so we need to capture this information the first time.
86     // TODO: Set this flag to FORCE_DEBUG once the new conference call logic gets more mileage
87     // across different IMS implementations.
88     private static final boolean CONF_DBG = true;
89 
90     private List<ConferenceParticipant> mConferenceParticipants;
91     /**
92      * Listener for events relating to an IMS call, such as when a call is being
93      * received ("on ringing") or a call is outgoing ("on calling").
94      * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
95      */
96     public static class Listener {
97         /**
98          * Called after the network first begins to establish the call session and is now connecting
99          * to the remote party.
100          * The default implementation calls {@link #onCallStateChanged}.
101          * <p/>
102          * see: {@link ImsCallSessionListener#callSessionInitiating}
103          */
onCallInitiating(ImsCall call)104         public void onCallInitiating(ImsCall call) {
105             onCallStateChanged(call);
106         }
107 
108         /**
109          * Called after the network has contacted the remote party.
110          * The default implementation calls {@link #onCallStateChanged}.
111          * <p/>
112          * see: {@link ImsCallSessionListener#callSessionProgressing}
113          */
onCallProgressing(ImsCall call)114         public void onCallProgressing(ImsCall call) {
115             onCallStateChanged(call);
116         }
117 
118         /**
119          * Called when the call is established.
120          * The default implementation calls {@link #onCallStateChanged}.
121          *
122          * @param call the call object that carries out the IMS call
123          */
onCallStarted(ImsCall call)124         public void onCallStarted(ImsCall call) {
125             onCallStateChanged(call);
126         }
127 
128         /**
129          * Called when the call setup is failed.
130          * The default implementation calls {@link #onCallError}.
131          *
132          * @param call the call object that carries out the IMS call
133          * @param reasonInfo detailed reason of the call setup failure
134          */
onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo)135         public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
136             onCallError(call, reasonInfo);
137         }
138 
139         /**
140          * Called when the call is terminated.
141          * The default implementation calls {@link #onCallStateChanged}.
142          *
143          * @param call the call object that carries out the IMS call
144          * @param reasonInfo detailed reason of the call termination
145          */
onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo)146         public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
147             // Store the call termination reason
148 
149             onCallStateChanged(call);
150         }
151 
152         /**
153          * Called when the call is in hold.
154          * The default implementation calls {@link #onCallStateChanged}.
155          *
156          * @param call the call object that carries out the IMS call
157          */
onCallHeld(ImsCall call)158         public void onCallHeld(ImsCall call) {
159             onCallStateChanged(call);
160         }
161 
162         /**
163          * Called when the call hold is failed.
164          * The default implementation calls {@link #onCallError}.
165          *
166          * @param call the call object that carries out the IMS call
167          * @param reasonInfo detailed reason of the call hold failure
168          */
onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo)169         public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
170             onCallError(call, reasonInfo);
171         }
172 
173         /**
174          * Called when the call hold is received from the remote user.
175          * The default implementation calls {@link #onCallStateChanged}.
176          *
177          * @param call the call object that carries out the IMS call
178          */
onCallHoldReceived(ImsCall call)179         public void onCallHoldReceived(ImsCall call) {
180             onCallStateChanged(call);
181         }
182 
183         /**
184          * Called when the call is in call.
185          * The default implementation calls {@link #onCallStateChanged}.
186          *
187          * @param call the call object that carries out the IMS call
188          */
onCallResumed(ImsCall call)189         public void onCallResumed(ImsCall call) {
190             onCallStateChanged(call);
191         }
192 
193         /**
194          * Called when the call resume is failed.
195          * The default implementation calls {@link #onCallError}.
196          *
197          * @param call the call object that carries out the IMS call
198          * @param reasonInfo detailed reason of the call resume failure
199          */
onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo)200         public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
201             onCallError(call, reasonInfo);
202         }
203 
204         /**
205          * Called when the call resume is received from the remote user.
206          * The default implementation calls {@link #onCallStateChanged}.
207          *
208          * @param call the call object that carries out the IMS call
209          */
onCallResumeReceived(ImsCall call)210         public void onCallResumeReceived(ImsCall call) {
211             onCallStateChanged(call);
212         }
213 
214         /**
215          * Called when the call is in call.
216          * The default implementation calls {@link #onCallStateChanged}.
217          *
218          * @param call the call object that carries out the active IMS call
219          * @param peerCall the call object that carries out the held IMS call
220          * @param swapCalls {@code true} if the foreground and background calls should be swapped
221          *                              now that the merge has completed.
222          */
onCallMerged(ImsCall call, ImsCall peerCall, boolean swapCalls)223         public void onCallMerged(ImsCall call, ImsCall peerCall, boolean swapCalls) {
224             onCallStateChanged(call);
225         }
226 
227         /**
228          * Called when the call merge is failed.
229          * The default implementation calls {@link #onCallError}.
230          *
231          * @param call the call object that carries out the IMS call
232          * @param reasonInfo detailed reason of the call merge failure
233          */
onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo)234         public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
235             onCallError(call, reasonInfo);
236         }
237 
238         /**
239          * Called when the call is updated (except for hold/unhold).
240          * The default implementation calls {@link #onCallStateChanged}.
241          *
242          * @param call the call object that carries out the IMS call
243          */
onCallUpdated(ImsCall call)244         public void onCallUpdated(ImsCall call) {
245             onCallStateChanged(call);
246         }
247 
248         /**
249          * Called when the call update is failed.
250          * The default implementation calls {@link #onCallError}.
251          *
252          * @param call the call object that carries out the IMS call
253          * @param reasonInfo detailed reason of the call update failure
254          */
onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo)255         public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
256             onCallError(call, reasonInfo);
257         }
258 
259         /**
260          * Called when the call update is received from the remote user.
261          *
262          * @param call the call object that carries out the IMS call
263          */
onCallUpdateReceived(ImsCall call)264         public void onCallUpdateReceived(ImsCall call) {
265             // no-op
266         }
267 
268         /**
269          * Called when the call is extended to the conference call.
270          * The default implementation calls {@link #onCallStateChanged}.
271          *
272          * @param call the call object that carries out the IMS call
273          * @param newCall the call object that is extended to the conference from the active call
274          */
onCallConferenceExtended(ImsCall call, ImsCall newCall)275         public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
276             onCallStateChanged(call);
277         }
278 
279         /**
280          * Called when the conference extension is failed.
281          * The default implementation calls {@link #onCallError}.
282          *
283          * @param call the call object that carries out the IMS call
284          * @param reasonInfo detailed reason of the conference extension failure
285          */
onCallConferenceExtendFailed(ImsCall call, ImsReasonInfo reasonInfo)286         public void onCallConferenceExtendFailed(ImsCall call,
287                 ImsReasonInfo reasonInfo) {
288             onCallError(call, reasonInfo);
289         }
290 
291         /**
292          * Called when the conference extension is received from the remote user.
293          *
294          * @param call the call object that carries out the IMS call
295          * @param newCall the call object that is extended to the conference from the active call
296          */
onCallConferenceExtendReceived(ImsCall call, ImsCall newCall)297         public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
298             onCallStateChanged(call);
299         }
300 
301         /**
302          * Called when the invitation request of the participants is delivered to
303          * the conference server.
304          *
305          * @param call the call object that carries out the IMS call
306          */
onCallInviteParticipantsRequestDelivered(ImsCall call)307         public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
308             // no-op
309         }
310 
311         /**
312          * Called when the invitation request of the participants is failed.
313          *
314          * @param call the call object that carries out the IMS call
315          * @param reasonInfo detailed reason of the conference invitation failure
316          */
onCallInviteParticipantsRequestFailed(ImsCall call, ImsReasonInfo reasonInfo)317         public void onCallInviteParticipantsRequestFailed(ImsCall call,
318                 ImsReasonInfo reasonInfo) {
319             // no-op
320         }
321 
322         /**
323          * Called when the removal request of the participants is delivered to
324          * the conference server.
325          *
326          * @param call the call object that carries out the IMS call
327          */
onCallRemoveParticipantsRequestDelivered(ImsCall call)328         public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
329             // no-op
330         }
331 
332         /**
333          * Called when the removal request of the participants is failed.
334          *
335          * @param call the call object that carries out the IMS call
336          * @param reasonInfo detailed reason of the conference removal failure
337          */
onCallRemoveParticipantsRequestFailed(ImsCall call, ImsReasonInfo reasonInfo)338         public void onCallRemoveParticipantsRequestFailed(ImsCall call,
339                 ImsReasonInfo reasonInfo) {
340             // no-op
341         }
342 
343         /**
344          * Called when the conference state is updated.
345          *
346          * @param call the call object that carries out the IMS call
347          * @param state state of the participant who is participated in the conference call
348          */
onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state)349         public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
350             // no-op
351         }
352 
353         /**
354          * Called when the state of IMS conference participant(s) has changed.
355          *
356          * @param call the call object that carries out the IMS call.
357          * @param participants the participant(s) and their new state information.
358          */
onConferenceParticipantsStateChanged(ImsCall call, List<ConferenceParticipant> participants)359         public void onConferenceParticipantsStateChanged(ImsCall call,
360                 List<ConferenceParticipant> participants) {
361             // no-op
362         }
363 
364         /**
365          * Called when the USSD message is received from the network.
366          *
367          * @param mode mode of the USSD message (REQUEST / NOTIFY)
368          * @param ussdMessage USSD message
369          */
onCallUssdMessageReceived(ImsCall call, int mode, String ussdMessage)370         public void onCallUssdMessageReceived(ImsCall call,
371                 int mode, String ussdMessage) {
372             // no-op
373         }
374 
375         /**
376          * Called when an error occurs. The default implementation is no op.
377          * overridden. The default implementation is no op. Error events are
378          * not re-directed to this callback and are handled in {@link #onCallError}.
379          *
380          * @param call the call object that carries out the IMS call
381          * @param reasonInfo detailed reason of this error
382          * @see ImsReasonInfo
383          */
onCallError(ImsCall call, ImsReasonInfo reasonInfo)384         public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
385             // no-op
386         }
387 
388         /**
389          * Called when an event occurs and the corresponding callback is not
390          * overridden. The default implementation is no op. Error events are
391          * not re-directed to this callback and are handled in {@link #onCallError}.
392          *
393          * @param call the call object that carries out the IMS call
394          */
onCallStateChanged(ImsCall call)395         public void onCallStateChanged(ImsCall call) {
396             // no-op
397         }
398 
399         /**
400          * Called when the call moves the hold state to the conversation state.
401          * For example, when merging the active & hold call, the state of all the hold call
402          * will be changed from hold state to conversation state.
403          * This callback method can be invoked even though the application does not trigger
404          * any operations.
405          *
406          * @param call the call object that carries out the IMS call
407          * @param state the detailed state of call state changes;
408          *      Refer to CALL_STATE_* in {@link ImsCall}
409          */
onCallStateChanged(ImsCall call, int state)410         public void onCallStateChanged(ImsCall call, int state) {
411             // no-op
412         }
413 
414         /**
415          * Called when the call supp service is received
416          * The default implementation calls {@link #onCallStateChanged}.
417          *
418          * @param call the call object that carries out the IMS call
419          */
onCallSuppServiceReceived(ImsCall call, ImsSuppServiceNotification suppServiceInfo)420         public void onCallSuppServiceReceived(ImsCall call,
421             ImsSuppServiceNotification suppServiceInfo) {
422         }
423 
424         /**
425          * Called when TTY mode of remote party changed
426          *
427          * @param call the call object that carries out the IMS call
428          * @param mode TTY mode of remote party
429          */
onCallSessionTtyModeReceived(ImsCall call, int mode)430         public void onCallSessionTtyModeReceived(ImsCall call, int mode) {
431             // no-op
432         }
433 
434         /**
435          * Called when handover occurs from one access technology to another.
436          *
437          * @param imsCall ImsCall object
438          * @param srcAccessTech original access technology
439          * @param targetAccessTech new access technology
440          * @param reasonInfo
441          */
onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech, ImsReasonInfo reasonInfo)442         public void onCallHandover(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
443             ImsReasonInfo reasonInfo) {
444         }
445 
446         /**
447          * Called when the remote party issues an RTT modify request
448          *
449          * @param imsCall ImsCall object
450          */
onRttModifyRequestReceived(ImsCall imsCall)451         public void onRttModifyRequestReceived(ImsCall imsCall) {
452         }
453 
454         /**
455          * Called when the remote party responds to a locally-issued RTT request.
456          *
457          * @param imsCall ImsCall object
458          * @param status The status of the request. See
459          *               {@link Connection.RttModifyStatus} for possible values.
460          */
onRttModifyResponseReceived(ImsCall imsCall, int status)461         public void onRttModifyResponseReceived(ImsCall imsCall, int status) {
462         }
463 
464         /**
465          * Called when the remote party has sent some characters via RTT
466          *
467          * @param imsCall ImsCall object
468          * @param message A string containing the transmitted characters.
469          */
onRttMessageReceived(ImsCall imsCall, String message)470         public void onRttMessageReceived(ImsCall imsCall, String message) {
471         }
472 
473         /**
474          * Called when handover from one access technology to another fails.
475          *
476          * @param imsCall call that failed the handover.
477          * @param srcAccessTech original access technology
478          * @param targetAccessTech new access technology
479          * @param reasonInfo
480          */
onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech, ImsReasonInfo reasonInfo)481         public void onCallHandoverFailed(ImsCall imsCall, int srcAccessTech, int targetAccessTech,
482             ImsReasonInfo reasonInfo) {
483         }
484 
485         /**
486          * Notifies of a change to the multiparty state for this {@code ImsCall}.
487          *
488          * @param imsCall The IMS call.
489          * @param isMultiParty {@code true} if the call became multiparty, {@code false}
490          *      otherwise.
491          */
onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty)492         public void onMultipartyStateChanged(ImsCall imsCall, boolean isMultiParty) {
493         }
494 
495         /**
496          * Called when rtt call audio indicator has changed.
497          *
498          * @param imsCall ImsCall object
499          * @param profile updated ImsStreamMediaProfile profile.
500          */
onRttAudioIndicatorChanged(ImsCall imsCall, ImsStreamMediaProfile profile)501         public void onRttAudioIndicatorChanged(ImsCall imsCall, ImsStreamMediaProfile profile) {
502         }
503 
504         /**
505          * Notifies the result of transfer request.
506          *
507          * @param imsCall ImsCall object
508          */
onCallSessionTransferred(ImsCall imsCall)509         public void onCallSessionTransferred(ImsCall imsCall) {
510         }
511 
onCallSessionTransferFailed(ImsCall imsCall, ImsReasonInfo reasonInfo)512         public void onCallSessionTransferFailed(ImsCall imsCall, ImsReasonInfo reasonInfo) {
513         }
514 
515         /**
516          * Reports a DTMF tone received from the network.
517          * @param imsCall The IMS call the tone was received from.
518          * @param digit The digit received.
519          */
onCallSessionDtmfReceived(ImsCall imsCall, char digit)520         public void onCallSessionDtmfReceived(ImsCall imsCall, char digit) {
521         }
522 
523         /**
524          * Called when the call quality has changed.
525          *
526          * @param imsCall ImsCall object
527          * @param callQuality the updated CallQuality
528          */
onCallQualityChanged(ImsCall imsCall, CallQuality callQuality)529         public void onCallQualityChanged(ImsCall imsCall, CallQuality callQuality) {
530         }
531 
532         /**
533          * Called when RTP header extension data is received from the network.
534          * @param imsCall The ImsCall the data was received on.
535          * @param rtpHeaderExtensionData The RTP extension data received.
536          */
onCallSessionRtpHeaderExtensionsReceived(ImsCall imsCall, @NonNull Set<RtpHeaderExtension> rtpHeaderExtensionData)537         public void onCallSessionRtpHeaderExtensionsReceived(ImsCall imsCall,
538                 @NonNull Set<RtpHeaderExtension> rtpHeaderExtensionData) {
539         }
540 
541         /**
542         * 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                 mCallProfile.mMediaProfile.copyFrom(profile);
2542             }
2543 
2544             if (listener != null) {
2545                 try {
2546                     listener.onCallProgressing(ImsCall.this);
2547                 } catch (Throwable t) {
2548                     loge("callSessionProgressing :: ", t);
2549                 }
2550             }
2551         }
2552 
2553         @Override
callSessionStarted(ImsCallSession session, ImsCallProfile profile)2554         public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
2555             logi("callSessionStarted :: session=" + session + " profile=" + profile);
2556 
2557             if (!isTransientConferenceSession(session)) {
2558                 // In the case that we are in the middle of a merge (either host or peer), we have
2559                 // closure as far as this call's primary session is concerned.  If we are not
2560                 // merging...its a NOOP.
2561                 setCallSessionMergePending(false);
2562             } else {
2563                 logi("callSessionStarted :: on transient session=" + session);
2564                 return;
2565             }
2566 
2567             if (isTransientConferenceSession(session)) {
2568                 // No further processing is needed if this is the transient session.
2569                 return;
2570             }
2571 
2572             ImsCall.Listener listener;
2573 
2574             synchronized(ImsCall.this) {
2575                 listener = mListener;
2576                 setCallProfile(profile);
2577             }
2578 
2579             if (listener != null) {
2580                 try {
2581                     listener.onCallStarted(ImsCall.this);
2582                 } catch (Throwable t) {
2583                     loge("callSessionStarted :: ", t);
2584                 }
2585             }
2586         }
2587 
2588         @Override
callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2589         public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2590             loge("callSessionStartFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2591 
2592             if (isTransientConferenceSession(session)) {
2593                 // We should not get this callback for a transient session.
2594                 logi("callSessionStartFailed :: not supported for transient conference session=" +
2595                         session);
2596                 return;
2597             }
2598 
2599             if (mIsConferenceHost) {
2600                 // If the dial request was a adhoc conf calling one, this call would have
2601                 // been marked the conference host as part of the request.
2602                 mIsConferenceHost = false;
2603             }
2604 
2605             ImsCall.Listener listener;
2606 
2607             synchronized(ImsCall.this) {
2608                 listener = mListener;
2609                 mLastReasonInfo = reasonInfo;
2610             }
2611 
2612             if (listener != null) {
2613                 try {
2614                     listener.onCallStartFailed(ImsCall.this, reasonInfo);
2615                 } catch (Throwable t) {
2616                     loge("callSessionStarted :: ", t);
2617                 }
2618             }
2619         }
2620 
2621         @Override
callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo)2622         public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
2623             logi("callSessionTerminated :: session=" + session + " reasonInfo=" + reasonInfo);
2624 
2625             if (isTransientConferenceSession(session)) {
2626                 logi("callSessionTerminated :: on transient session=" + session);
2627                 // This is bad, it should be treated much a callSessionMergeFailed since the
2628                 // transient session only exists when in the process of a merge and the
2629                 // termination of this session is effectively the end of the merge.
2630                 processMergeFailed(reasonInfo);
2631                 return;
2632             }
2633 
2634             if (mOverrideReason != ImsReasonInfo.CODE_UNSPECIFIED) {
2635                 logi("callSessionTerminated :: overrideReasonInfo=" + mOverrideReason);
2636                 reasonInfo = new ImsReasonInfo(mOverrideReason, reasonInfo.getExtraCode(),
2637                         reasonInfo.getExtraMessage());
2638             }
2639 
2640             // Process the termination first.  If we are in the midst of establishing a conference
2641             // call, we may bury this callback until we are done.  If there so no conference
2642             // call, the code after this function will be a NOOP.
2643             processCallTerminated(reasonInfo);
2644 
2645             // If session has terminated, it is no longer pending merge.
2646             setCallSessionMergePending(false);
2647 
2648         }
2649 
2650         @Override
callSessionHeld(ImsCallSession session, ImsCallProfile profile)2651         public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
2652             logi("callSessionHeld :: session=" + session + "profile=" + profile);
2653             ImsCall.Listener listener;
2654 
2655             synchronized(ImsCall.this) {
2656                 // If the session was held, it is no longer pending a merge -- this means it could
2657                 // not be merged into the conference and was held instead.
2658                 setCallSessionMergePending(false);
2659 
2660                 setCallProfile(profile);
2661 
2662                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2663                     // This hold request was made to set the stage for a merge.
2664                     mergeInternal();
2665                     return;
2666                 }
2667 
2668                 mHold = true;
2669                 mUpdateRequest = UPDATE_NONE;
2670                 listener = mListener;
2671             }
2672 
2673             if (listener != null) {
2674                 try {
2675                     listener.onCallHeld(ImsCall.this);
2676                 } catch (Throwable t) {
2677                     loge("callSessionHeld :: ", t);
2678                 }
2679             }
2680         }
2681 
2682         @Override
callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2683         public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2684             loge("callSessionHoldFailed :: session" + session + "reasonInfo=" + reasonInfo);
2685 
2686             if (isTransientConferenceSession(session)) {
2687                 // We should not get this callback for a transient session.
2688                 logi("callSessionHoldFailed :: not supported for transient conference session=" +
2689                         session);
2690                 return;
2691             }
2692 
2693             logi("callSessionHoldFailed :: session=" + session +
2694                     ", reasonInfo=" + reasonInfo);
2695 
2696             synchronized (mLockObj) {
2697                 mHold = false;
2698             }
2699 
2700             boolean isHoldForMerge = false;
2701             ImsCall.Listener listener;
2702 
2703             synchronized(ImsCall.this) {
2704                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
2705                     isHoldForMerge = true;
2706                 }
2707 
2708                 mUpdateRequest = UPDATE_NONE;
2709                 listener = mListener;
2710             }
2711 
2712             if (listener != null) {
2713                 try {
2714                     listener.onCallHoldFailed(ImsCall.this, reasonInfo);
2715                 } catch (Throwable t) {
2716                     loge("callSessionHoldFailed :: ", t);
2717                 }
2718             }
2719         }
2720 
2721         /**
2722          * Indicates that an {@link ImsCallSession} has been remotely held.  This can be due to the
2723          * remote party holding the current call, or swapping between calls.
2724          * @param session the session which was held.
2725          * @param profile the profile for the held call.
2726          */
2727         @Override
callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile)2728         public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
2729             logi("callSessionHoldReceived :: session=" + session + "profile=" + profile);
2730 
2731             if (isTransientConferenceSession(session)) {
2732                 // We should not get this callback for a transient session.
2733                 logi("callSessionHoldReceived :: not supported for transient conference session=" +
2734                         session);
2735                 return;
2736             }
2737 
2738             ImsCall.Listener listener;
2739 
2740             synchronized(ImsCall.this) {
2741                 listener = mListener;
2742                 setCallProfile(profile);
2743             }
2744 
2745             if (listener != null) {
2746                 try {
2747                     listener.onCallHoldReceived(ImsCall.this);
2748                 } catch (Throwable t) {
2749                     loge("callSessionHoldReceived :: ", t);
2750                 }
2751             }
2752         }
2753 
2754         /**
2755          * Indicates that an {@link ImsCallSession} has been remotely resumed.  This can be due to
2756          * the remote party un-holding the current call, or swapping back to this call.
2757          * @param session the session which was resumed.
2758          * @param profile the profile for the held call.
2759          */
2760         @Override
callSessionResumed(ImsCallSession session, ImsCallProfile profile)2761         public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
2762             logi("callSessionResumed :: session=" + session + "profile=" + profile);
2763 
2764             if (isTransientConferenceSession(session)) {
2765                 logi("callSessionResumed :: not supported for transient conference session=" +
2766                         session);
2767                 return;
2768             }
2769 
2770             // If this call was pending a merge, it is not anymore. This is the case when we
2771             // are merging in a new call into an existing conference.
2772             setCallSessionMergePending(false);
2773 
2774             // TOOD: When we are merging a new call into an existing conference we are waiting
2775             // for 2 triggers to let us know that the conference has been established, the first
2776             // is a termination for the new calls (since it is added to the conference) the second
2777             // would be a resume on the existing conference.  If the resume comes first, then
2778             // we will make the onCallResumed() callback and its unclear how this will behave if
2779             // the termination has not come yet.
2780 
2781             ImsCall.Listener listener;
2782             synchronized(ImsCall.this) {
2783                 listener = mListener;
2784                 setCallProfile(profile);
2785                 mUpdateRequest = UPDATE_NONE;
2786                 mHold = false;
2787             }
2788 
2789             if (listener != null) {
2790                 try {
2791                     listener.onCallResumed(ImsCall.this);
2792                 } catch (Throwable t) {
2793                     loge("callSessionResumed :: ", t);
2794                 }
2795             }
2796         }
2797 
2798         @Override
callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2799         public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2800             loge("callSessionResumeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2801 
2802             if (isTransientConferenceSession(session)) {
2803                 logi("callSessionResumeFailed :: not supported for transient conference session=" +
2804                         session);
2805                 return;
2806             }
2807 
2808             synchronized(mLockObj) {
2809                 mHold = true;
2810             }
2811 
2812             ImsCall.Listener listener;
2813 
2814             synchronized(ImsCall.this) {
2815                 listener = mListener;
2816                 mUpdateRequest = UPDATE_NONE;
2817             }
2818 
2819             if (listener != null) {
2820                 try {
2821                     listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2822                 } catch (Throwable t) {
2823                     loge("callSessionResumeFailed :: ", t);
2824                 }
2825             }
2826         }
2827 
2828         @Override
callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile)2829         public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
2830             logi("callSessionResumeReceived :: session=" + session + "profile=" + profile);
2831 
2832             if (isTransientConferenceSession(session)) {
2833                 logi("callSessionResumeReceived :: not supported for transient conference session=" +
2834                         session);
2835                 return;
2836             }
2837 
2838             ImsCall.Listener listener;
2839 
2840             synchronized(ImsCall.this) {
2841                 listener = mListener;
2842                 setCallProfile(profile);
2843             }
2844 
2845             if (listener != null) {
2846                 try {
2847                     listener.onCallResumeReceived(ImsCall.this);
2848                 } catch (Throwable t) {
2849                     loge("callSessionResumeReceived :: ", t);
2850                 }
2851             }
2852         }
2853 
2854         @Override
callSessionMergeStarted(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)2855         public void callSessionMergeStarted(ImsCallSession session,
2856                 ImsCallSession newSession, ImsCallProfile profile) {
2857             logi("callSessionMergeStarted :: session=" + session + " newSession=" + newSession +
2858                     ", profile=" + profile);
2859 
2860             return;
2861         }
2862 
2863         /**
2864          * We received a callback from ImsCallSession that merge completed.
2865          * @param newSession - this session can have 2 values based on the below scenarios
2866          *
2867 	 * Conference Scenarios :
2868          * Case 1 - 3 way success case
2869          * Case 2 - 3 way success case but held call fails to merge
2870          * Case 3 - 3 way success case but active call fails to merge
2871          * case 4 - 4 way success case, where merge is initiated on the foreground single-party
2872          *          call and the conference (mergeHost) is the background call.
2873          * case 5 - 4 way success case, where merge is initiated on the foreground conference
2874          *          call (mergeHost) and the single party call is in the background.
2875          *
2876          * Conference Result:
2877          * session : new session after conference
2878          * newSession = new session for case 1, 2, 3.
2879          *              Should be considered as mTransientConferencession
2880          * newSession = Active conference session for case 5 will be null
2881          *              mergehost was foreground call
2882          *              mTransientConferencession will be null
2883          * newSession = Active conference session for case 4 will be null
2884          *              mergeHost was background call
2885          *              mTransientConferencession will be null
2886          */
2887         @Override
callSessionMergeComplete(ImsCallSession newSession)2888         public void callSessionMergeComplete(ImsCallSession newSession) {
2889             logi("callSessionMergeComplete :: newSession =" + newSession);
2890             if (!isMergeHost()) {
2891                 // Handles case 4
2892                 mMergeHost.processMergeComplete();
2893             } else {
2894                 // Handles case 1, 2, 3
2895                 if (newSession != null) {
2896                     mTransientConferenceSession = newSession;
2897                 }
2898                 // Handles case 5
2899                 processMergeComplete();
2900             }
2901         }
2902 
2903         @Override
callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2904         public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2905             loge("callSessionMergeFailed :: session=" + session + "reasonInfo=" + reasonInfo);
2906 
2907             // Its possible that there could be threading issues with the other thread handling
2908             // the other call. This could affect our state.
2909             synchronized (ImsCall.this) {
2910                 // Let's tell our parent ImsCall that the merge has failed and we need to clean
2911                 // up any temporary, transient state.  Note this only gets called for an initial
2912                 // conference.  If a merge into an existing conference fails, the two sessions will
2913                 // just go back to their original state (ACTIVE or HELD).
2914                 if (isMergeHost()) {
2915                     processMergeFailed(reasonInfo);
2916                 } else if (mMergeHost != null) {
2917                     mMergeHost.processMergeFailed(reasonInfo);
2918                 } else {
2919                     loge("callSessionMergeFailed :: No merge host for this conference!");
2920                 }
2921             }
2922         }
2923 
2924         @Override
callSessionUpdated(ImsCallSession session, ImsCallProfile profile)2925         public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2926             logi("callSessionUpdated :: session=" + session + " profile=" + profile);
2927 
2928             if (isTransientConferenceSession(session)) {
2929                 logi("callSessionUpdated :: not supported for transient conference session=" +
2930                         session);
2931                 return;
2932             }
2933 
2934             ImsCall.Listener listener;
2935 
2936             synchronized(ImsCall.this) {
2937                 listener = mListener;
2938                 setCallProfile(profile);
2939             }
2940 
2941             if (listener != null) {
2942                 try {
2943                     listener.onCallUpdated(ImsCall.this);
2944                 } catch (Throwable t) {
2945                     loge("callSessionUpdated :: ", t);
2946                 }
2947             }
2948         }
2949 
2950         @Override
callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo)2951         public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2952             loge("callSessionUpdateFailed :: session=" + session + " reasonInfo=" + reasonInfo);
2953 
2954             if (isTransientConferenceSession(session)) {
2955                 logi("callSessionUpdateFailed :: not supported for transient conference session=" +
2956                         session);
2957                 return;
2958             }
2959 
2960             ImsCall.Listener listener;
2961 
2962             synchronized(ImsCall.this) {
2963                 listener = mListener;
2964                 mUpdateRequest = UPDATE_NONE;
2965             }
2966 
2967             if (listener != null) {
2968                 try {
2969                     listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2970                 } catch (Throwable t) {
2971                     loge("callSessionUpdateFailed :: ", t);
2972                 }
2973             }
2974         }
2975 
2976         @Override
callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile)2977         public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2978             logi("callSessionUpdateReceived :: session=" + session + " profile=" + profile);
2979 
2980             if (isTransientConferenceSession(session)) {
2981                 logi("callSessionUpdateReceived :: not supported for transient conference " +
2982                         "session=" + session);
2983                 return;
2984             }
2985 
2986             ImsCall.Listener listener;
2987 
2988             synchronized(ImsCall.this) {
2989                 listener = mListener;
2990                 mProposedCallProfile = profile;
2991                 mUpdateRequest = UPDATE_UNSPECIFIED;
2992             }
2993 
2994             if (listener != null) {
2995                 try {
2996                     listener.onCallUpdateReceived(ImsCall.this);
2997                 } catch (Throwable t) {
2998                     loge("callSessionUpdateReceived :: ", t);
2999                 }
3000             }
3001         }
3002 
3003         @Override
callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)3004         public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
3005                 ImsCallProfile profile) {
3006             logi("callSessionConferenceExtended :: session=" + session  + " newSession=" +
3007                     newSession + ", profile=" + profile);
3008 
3009             if (isTransientConferenceSession(session)) {
3010                 logi("callSessionConferenceExtended :: not supported for transient conference " +
3011                         "session=" + session);
3012                 return;
3013             }
3014 
3015             ImsCall newCall = createNewCall(newSession, profile);
3016 
3017             if (newCall == null) {
3018                 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
3019                 return;
3020             }
3021 
3022             ImsCall.Listener listener;
3023 
3024             synchronized(ImsCall.this) {
3025                 listener = mListener;
3026                 mUpdateRequest = UPDATE_NONE;
3027             }
3028 
3029             if (listener != null) {
3030                 try {
3031                     listener.onCallConferenceExtended(ImsCall.this, newCall);
3032                 } catch (Throwable t) {
3033                     loge("callSessionConferenceExtended :: ", t);
3034                 }
3035             }
3036         }
3037 
3038         @Override
callSessionConferenceExtendFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3039         public void callSessionConferenceExtendFailed(ImsCallSession session,
3040                 ImsReasonInfo reasonInfo) {
3041             loge("callSessionConferenceExtendFailed :: reasonInfo=" + reasonInfo);
3042 
3043             if (isTransientConferenceSession(session)) {
3044                 logi("callSessionConferenceExtendFailed :: not supported for transient " +
3045                         "conference session=" + session);
3046                 return;
3047             }
3048 
3049             ImsCall.Listener listener;
3050 
3051             synchronized(ImsCall.this) {
3052                 listener = mListener;
3053                 mUpdateRequest = UPDATE_NONE;
3054             }
3055 
3056             if (listener != null) {
3057                 try {
3058                     listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
3059                 } catch (Throwable t) {
3060                     loge("callSessionConferenceExtendFailed :: ", t);
3061                 }
3062             }
3063         }
3064 
3065         @Override
callSessionConferenceExtendReceived(ImsCallSession session, ImsCallSession newSession, ImsCallProfile profile)3066         public void callSessionConferenceExtendReceived(ImsCallSession session,
3067                 ImsCallSession newSession, ImsCallProfile profile) {
3068             logi("callSessionConferenceExtendReceived :: newSession=" + newSession +
3069                     ", profile=" + profile);
3070 
3071             if (isTransientConferenceSession(session)) {
3072                 logi("callSessionConferenceExtendReceived :: not supported for transient " +
3073                         "conference session" + session);
3074                 return;
3075             }
3076 
3077             ImsCall newCall = createNewCall(newSession, profile);
3078 
3079             if (newCall == null) {
3080                 // Should all the calls be terminated...???
3081                 return;
3082             }
3083 
3084             ImsCall.Listener listener;
3085 
3086             synchronized(ImsCall.this) {
3087                 listener = mListener;
3088             }
3089 
3090             if (listener != null) {
3091                 try {
3092                     listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
3093                 } catch (Throwable t) {
3094                     loge("callSessionConferenceExtendReceived :: ", t);
3095                 }
3096             }
3097         }
3098 
3099         @Override
callSessionInviteParticipantsRequestDelivered(ImsCallSession session)3100         public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
3101             logi("callSessionInviteParticipantsRequestDelivered ::");
3102 
3103             if (isTransientConferenceSession(session)) {
3104                 logi("callSessionInviteParticipantsRequestDelivered :: not supported for " +
3105                         "conference session=" + session);
3106                 return;
3107             }
3108 
3109             ImsCall.Listener listener;
3110 
3111             synchronized(ImsCall.this) {
3112                 listener = mListener;
3113             }
3114 
3115             mIsConferenceHost = true;
3116 
3117             if (listener != null) {
3118                 try {
3119                     listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
3120                 } catch (Throwable t) {
3121                     loge("callSessionInviteParticipantsRequestDelivered :: ", t);
3122                 }
3123             }
3124         }
3125 
3126         @Override
callSessionInviteParticipantsRequestFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3127         public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
3128                 ImsReasonInfo reasonInfo) {
3129             loge("callSessionInviteParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
3130 
3131             if (isTransientConferenceSession(session)) {
3132                 logi("callSessionInviteParticipantsRequestFailed :: not supported for " +
3133                         "conference session=" + session);
3134                 return;
3135             }
3136 
3137             ImsCall.Listener listener;
3138 
3139             synchronized(ImsCall.this) {
3140                 listener = mListener;
3141             }
3142 
3143             if (listener != null) {
3144                 try {
3145                     listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
3146                 } catch (Throwable t) {
3147                     loge("callSessionInviteParticipantsRequestFailed :: ", t);
3148                 }
3149             }
3150         }
3151 
3152         @Override
callSessionRemoveParticipantsRequestDelivered(ImsCallSession session)3153         public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
3154             logi("callSessionRemoveParticipantsRequestDelivered ::");
3155 
3156             if (isTransientConferenceSession(session)) {
3157                 logi("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
3158                         "conference session=" + session);
3159                 return;
3160             }
3161 
3162             ImsCall.Listener listener;
3163 
3164             synchronized(ImsCall.this) {
3165                 listener = mListener;
3166             }
3167 
3168             if (listener != null) {
3169                 try {
3170                     listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
3171                 } catch (Throwable t) {
3172                     loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
3173                 }
3174             }
3175         }
3176 
3177         @Override
callSessionRemoveParticipantsRequestFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3178         public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
3179                 ImsReasonInfo reasonInfo) {
3180             loge("callSessionRemoveParticipantsRequestFailed :: reasonInfo=" + reasonInfo);
3181 
3182             if (isTransientConferenceSession(session)) {
3183                 logi("callSessionRemoveParticipantsRequestFailed :: not supported for " +
3184                         "conference session=" + session);
3185                 return;
3186             }
3187 
3188             ImsCall.Listener listener;
3189 
3190             synchronized(ImsCall.this) {
3191                 listener = mListener;
3192             }
3193 
3194             if (listener != null) {
3195                 try {
3196                     listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
3197                 } catch (Throwable t) {
3198                     loge("callSessionRemoveParticipantsRequestFailed :: ", t);
3199                 }
3200             }
3201         }
3202 
3203         @Override
callSessionConferenceStateUpdated(ImsCallSession session, ImsConferenceState state)3204         public void callSessionConferenceStateUpdated(ImsCallSession session,
3205                 ImsConferenceState state) {
3206             logi("callSessionConferenceStateUpdated :: state=" + state);
3207             conferenceStateUpdated(state);
3208         }
3209 
3210         @Override
callSessionUssdMessageReceived(ImsCallSession session, int mode, String ussdMessage)3211         public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
3212                 String ussdMessage) {
3213             logi("callSessionUssdMessageReceived :: mode=" + mode + ", ussdMessage=" +
3214                     ussdMessage);
3215 
3216             if (isTransientConferenceSession(session)) {
3217                 logi("callSessionUssdMessageReceived :: not supported for transient " +
3218                         "conference session=" + session);
3219                 return;
3220             }
3221 
3222             ImsCall.Listener listener;
3223 
3224             synchronized(ImsCall.this) {
3225                 listener = mListener;
3226             }
3227 
3228             if (listener != null) {
3229                 try {
3230                     listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
3231                 } catch (Throwable t) {
3232                     loge("callSessionUssdMessageReceived :: ", t);
3233                 }
3234             }
3235         }
3236 
3237         @Override
callSessionTtyModeReceived(ImsCallSession session, int mode)3238         public void callSessionTtyModeReceived(ImsCallSession session, int mode) {
3239             logi("callSessionTtyModeReceived :: mode=" + mode);
3240 
3241             ImsCall.Listener listener;
3242 
3243             synchronized(ImsCall.this) {
3244                 listener = mListener;
3245             }
3246 
3247             if (listener != null) {
3248                 try {
3249                     listener.onCallSessionTtyModeReceived(ImsCall.this, mode);
3250                 } catch (Throwable t) {
3251                     loge("callSessionTtyModeReceived :: ", t);
3252                 }
3253             }
3254         }
3255 
3256         /**
3257          * Notifies of a change to the multiparty state for this {@code ImsCallSession}.
3258          *
3259          * @param session The call session.
3260          * @param isMultiParty {@code true} if the session became multiparty, {@code false}
3261          *      otherwise.
3262          */
3263         @Override
callSessionMultipartyStateChanged(ImsCallSession session, boolean isMultiParty)3264         public void callSessionMultipartyStateChanged(ImsCallSession session,
3265                 boolean isMultiParty) {
3266             if (VDBG) {
3267                 logi("callSessionMultipartyStateChanged isMultiParty: " + (isMultiParty ? "Y"
3268                         : "N"));
3269             }
3270 
3271             ImsCall.Listener listener;
3272 
3273             synchronized(ImsCall.this) {
3274                 listener = mListener;
3275             }
3276 
3277             if (listener != null) {
3278                 try {
3279                     listener.onMultipartyStateChanged(ImsCall.this, isMultiParty);
3280                 } catch (Throwable t) {
3281                     loge("callSessionMultipartyStateChanged :: ", t);
3282                 }
3283             }
3284         }
3285 
callSessionHandover(ImsCallSession session, int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo)3286         public void callSessionHandover(ImsCallSession session, int srcNetworkType,
3287             int targetNetworkType, ImsReasonInfo reasonInfo) {
3288             logi("callSessionHandover :: session=" + session + ", srcAccessTech=" +
3289                     srcNetworkType + ", targetAccessTech=" + targetNetworkType + ", reasonInfo=" +
3290                 reasonInfo);
3291 
3292             ImsCall.Listener listener;
3293 
3294             synchronized(ImsCall.this) {
3295                 listener = mListener;
3296             }
3297 
3298             if (listener != null) {
3299                 try {
3300                     listener.onCallHandover(ImsCall.this,
3301                             ServiceState.networkTypeToRilRadioTechnology(srcNetworkType),
3302                             ServiceState.networkTypeToRilRadioTechnology(targetNetworkType),
3303                             reasonInfo);
3304                 } catch (Throwable t) {
3305                     loge("callSessionHandover :: ", t);
3306                 }
3307             }
3308         }
3309 
3310         @Override
callSessionHandoverFailed(ImsCallSession session, int srcNetworkType, int targetNetworkType, ImsReasonInfo reasonInfo)3311         public void callSessionHandoverFailed(ImsCallSession session, int srcNetworkType,
3312             int targetNetworkType, ImsReasonInfo reasonInfo) {
3313             loge("callSessionHandoverFailed :: session=" + session + ", srcAccessTech=" +
3314                     srcNetworkType + ", targetAccessTech=" + targetNetworkType + ", reasonInfo=" +
3315                 reasonInfo);
3316 
3317             ImsCall.Listener listener;
3318 
3319             synchronized(ImsCall.this) {
3320                 listener = mListener;
3321             }
3322 
3323             if (listener != null) {
3324                 try {
3325                     listener.onCallHandoverFailed(ImsCall.this,
3326                             ServiceState.networkTypeToRilRadioTechnology(srcNetworkType),
3327                             ServiceState.networkTypeToRilRadioTechnology(targetNetworkType),
3328                             reasonInfo);
3329                 } catch (Throwable t) {
3330                     loge("callSessionHandoverFailed :: ", t);
3331                 }
3332             }
3333         }
3334 
3335         @Override
callSessionSuppServiceReceived(ImsCallSession session, ImsSuppServiceNotification suppServiceInfo )3336         public void callSessionSuppServiceReceived(ImsCallSession session,
3337                 ImsSuppServiceNotification suppServiceInfo ) {
3338             if (isTransientConferenceSession(session)) {
3339                 logi("callSessionSuppServiceReceived :: not supported for transient conference"
3340                         + " session=" + session);
3341                 return;
3342             }
3343 
3344             logi("callSessionSuppServiceReceived :: session=" + session +
3345                      ", suppServiceInfo" + suppServiceInfo);
3346 
3347             ImsCall.Listener listener;
3348 
3349             synchronized(ImsCall.this) {
3350                 listener = mListener;
3351             }
3352 
3353             if (listener != null) {
3354                 try {
3355                     listener.onCallSuppServiceReceived(ImsCall.this, suppServiceInfo);
3356                 } catch (Throwable t) {
3357                     loge("callSessionSuppServiceReceived :: ", t);
3358                 }
3359             }
3360         }
3361 
3362         @Override
callSessionRttModifyRequestReceived(ImsCallSession session, ImsCallProfile callProfile)3363         public void callSessionRttModifyRequestReceived(ImsCallSession session,
3364                 ImsCallProfile callProfile) {
3365             ImsCall.Listener listener;
3366             logi("callSessionRttModifyRequestReceived");
3367 
3368             synchronized(ImsCall.this) {
3369                 listener = mListener;
3370             }
3371 
3372             if (!callProfile.mMediaProfile.isRttCall()) {
3373                 logi("callSessionRttModifyRequestReceived:: ignoring request, requested profile " +
3374                         "is not RTT.");
3375                 return;
3376             }
3377 
3378             if (listener != null) {
3379                 try {
3380                     listener.onRttModifyRequestReceived(ImsCall.this);
3381                 } catch (Throwable t) {
3382                     loge("callSessionRttModifyRequestReceived:: ", t);
3383                 }
3384             }
3385         }
3386 
3387         @Override
callSessionRttModifyResponseReceived(int status)3388         public void callSessionRttModifyResponseReceived(int status) {
3389             ImsCall.Listener listener;
3390 
3391             logi("callSessionRttModifyResponseReceived: " + status);
3392             synchronized(ImsCall.this) {
3393                 listener = mListener;
3394             }
3395 
3396             if (listener != null) {
3397                 try {
3398                     listener.onRttModifyResponseReceived(ImsCall.this, status);
3399                 } catch (Throwable t) {
3400                     loge("callSessionRttModifyResponseReceived:: ", t);
3401                 }
3402             }
3403         }
3404 
3405         @Override
callSessionRttMessageReceived(String rttMessage)3406         public void callSessionRttMessageReceived(String rttMessage) {
3407             ImsCall.Listener listener;
3408 
3409             synchronized(ImsCall.this) {
3410                 listener = mListener;
3411             }
3412 
3413             if (listener != null) {
3414                 try {
3415                     listener.onRttMessageReceived(ImsCall.this, rttMessage);
3416                 } catch (Throwable t) {
3417                     loge("callSessionRttMessageReceived:: ", t);
3418                 }
3419             }
3420         }
3421 
3422         @Override
callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile)3423         public void callSessionRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
3424             ImsCall.Listener listener;
3425 
3426             synchronized(ImsCall.this) {
3427                 listener = mListener;
3428             }
3429 
3430             if (listener != null) {
3431                 try {
3432                     listener.onRttAudioIndicatorChanged(ImsCall.this, profile);
3433                 } catch (Throwable t) {
3434                     loge("callSessionRttAudioIndicatorChanged:: ", t);
3435                 }
3436             }
3437         }
3438 
3439         @Override
callSessionTransferred(ImsCallSession session)3440         public void callSessionTransferred(ImsCallSession session) {
3441             ImsCall.Listener listener;
3442 
3443             synchronized(ImsCall.this) {
3444                 listener = mListener;
3445             }
3446 
3447             if (listener != null) {
3448                 try {
3449                     listener.onCallSessionTransferred(ImsCall.this);
3450                 } catch (Throwable t) {
3451                     loge("callSessionTransferred:: ", t);
3452                 }
3453             }
3454         }
3455 
3456         @Override
callSessionTransferFailed(ImsCallSession session, ImsReasonInfo reasonInfo)3457         public void callSessionTransferFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
3458             ImsCall.Listener listener;
3459 
3460             synchronized(ImsCall.this) {
3461                 listener = mListener;
3462             }
3463 
3464             if (listener != null) {
3465                 try {
3466                     listener.onCallSessionTransferFailed(ImsCall.this, reasonInfo);
3467                 } catch (Throwable t) {
3468                     loge("callSessionTransferFailed:: ", t);
3469                 }
3470             }
3471         }
3472 
3473         @Override
callSessionDtmfReceived(char digit)3474         public void callSessionDtmfReceived(char digit) {
3475             ImsCall.Listener listener;
3476 
3477             synchronized(ImsCall.this) {
3478                 listener = mListener;
3479             }
3480 
3481             if (listener != null) {
3482                 try {
3483                     listener.onCallSessionDtmfReceived(ImsCall.this, digit);
3484                 } catch (Throwable t) {
3485                     loge("callSessionDtmfReceived:: ", t);
3486                 }
3487             }
3488         }
3489 
3490         @Override
callQualityChanged(CallQuality callQuality)3491         public void callQualityChanged(CallQuality callQuality) {
3492             ImsCall.Listener listener;
3493 
3494             synchronized (ImsCall.this) {
3495                 listener = mListener;
3496             }
3497 
3498             if (listener != null) {
3499                 try {
3500                     listener.onCallQualityChanged(ImsCall.this, callQuality);
3501                 } catch (Throwable t) {
3502                     loge("callQualityChanged:: ", t);
3503                 }
3504             }
3505         }
3506 
3507         @Override
callSessionRtpHeaderExtensionsReceived( @onNull Set<RtpHeaderExtension> extensions)3508         public void callSessionRtpHeaderExtensionsReceived(
3509                 @NonNull Set<RtpHeaderExtension> extensions) {
3510             ImsCall.Listener listener;
3511 
3512             synchronized (ImsCall.this) {
3513                 listener = mListener;
3514             }
3515 
3516             if (listener != null) {
3517                 try {
3518                     listener.onCallSessionRtpHeaderExtensionsReceived(ImsCall.this, extensions);
3519                 } catch (Throwable t) {
3520                     loge("callSessionRtpHeaderExtensionsReceived:: ", t);
3521                 }
3522             }
3523         }
3524 
3525         @Override
callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond)3526         public void callSessionSendAnbrQuery(int mediaType, int direction, int bitsPerSecond) {
3527             ImsCall.Listener listener;
3528 
3529             logi("callSessionSendAnbrQuery in ImsCall");
3530             synchronized (ImsCall.this) {
3531                 listener = mListener;
3532             }
3533 
3534             if (listener != null) {
3535                 try {
3536                     listener.onCallSessionSendAnbrQuery(ImsCall.this, mediaType,
3537                             direction, bitsPerSecond);
3538                 } catch (Throwable t) {
3539                     loge("callSessionSendAnbrQuery:: ", t);
3540                 }
3541             }
3542         }
3543     }
3544 
3545     /**
3546      * Report a new conference state to the current {@link ImsCall} and inform listeners of the
3547      * change.  Marked as {@code VisibleForTesting} so that the
3548      * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
3549      * event package into a regular ongoing IMS call.
3550      *
3551      * @param state The {@link ImsConferenceState}.
3552      */
3553     @VisibleForTesting
conferenceStateUpdated(ImsConferenceState state)3554     public void conferenceStateUpdated(ImsConferenceState state) {
3555         Listener listener;
3556 
3557         synchronized(this) {
3558             notifyConferenceStateUpdated(state);
3559             listener = mListener;
3560         }
3561 
3562         if (listener != null) {
3563             try {
3564                 listener.onCallConferenceStateUpdated(this, state);
3565             } catch (Throwable t) {
3566                 loge("callSessionConferenceStateUpdated :: ", t);
3567             }
3568         }
3569     }
3570 
3571     /**
3572      * Provides a human-readable string representation of an update request.
3573      *
3574      * @param updateRequest The update request.
3575      * @return The string representation.
3576      */
updateRequestToString(int updateRequest)3577     private String updateRequestToString(int updateRequest) {
3578         switch (updateRequest) {
3579             case UPDATE_NONE:
3580                 return "NONE";
3581             case UPDATE_HOLD:
3582                 return "HOLD";
3583             case UPDATE_HOLD_MERGE:
3584                 return "HOLD_MERGE";
3585             case UPDATE_RESUME:
3586                 return "RESUME";
3587             case UPDATE_MERGE:
3588                 return "MERGE";
3589             case UPDATE_EXTEND_TO_CONFERENCE:
3590                 return "EXTEND_TO_CONFERENCE";
3591             case UPDATE_UNSPECIFIED:
3592                 return "UNSPECIFIED";
3593             default:
3594                 return "UNKNOWN";
3595         }
3596     }
3597 
3598     /**
3599      * Clears the merge peer for this call, ensuring that the peer's connection to this call is also
3600      * severed at the same time.
3601      */
clearMergeInfo()3602     private void clearMergeInfo() {
3603         if (CONF_DBG) {
3604             logi("clearMergeInfo :: clearing all merge info");
3605         }
3606 
3607         // First clear out the merge partner then clear ourselves out.
3608         if (mMergeHost != null) {
3609             mMergeHost.mMergePeer = null;
3610             mMergeHost.mUpdateRequest = UPDATE_NONE;
3611             mMergeHost.mCallSessionMergePending = false;
3612         }
3613         if (mMergePeer != null) {
3614             mMergePeer.mMergeHost = null;
3615             mMergePeer.mUpdateRequest = UPDATE_NONE;
3616             mMergePeer.mCallSessionMergePending = false;
3617         }
3618         mMergeHost = null;
3619         mMergePeer = null;
3620         mUpdateRequest = UPDATE_NONE;
3621         mCallSessionMergePending = false;
3622     }
3623 
3624     /**
3625      * Sets the merge peer for the current call.  The merge peer is the background call that will be
3626      * merged into this call.  On the merge peer, sets the merge host to be this call.
3627      *
3628      * @param mergePeer The peer call to be merged into this one.
3629      */
setMergePeer(ImsCall mergePeer)3630     private void setMergePeer(ImsCall mergePeer) {
3631         mMergePeer = mergePeer;
3632         mMergeHost = null;
3633 
3634         mergePeer.mMergeHost = ImsCall.this;
3635         mergePeer.mMergePeer = null;
3636     }
3637 
3638     /**
3639      * Sets the merge hody for the current call.  The merge host is the foreground call this call
3640      * will be merged into.  On the merge host, sets the merge peer to be this call.
3641      *
3642      * @param mergeHost The merge host this call will be merged into.
3643      */
setMergeHost(ImsCall mergeHost)3644     public void setMergeHost(ImsCall mergeHost) {
3645         mMergeHost = mergeHost;
3646         mMergePeer = null;
3647 
3648         mergeHost.mMergeHost = null;
3649         mergeHost.mMergePeer = ImsCall.this;
3650     }
3651 
3652     /**
3653      * Determines if the current call is in the process of merging with another call or conference.
3654      *
3655      * @return {@code true} if in the process of merging.
3656      */
isMerging()3657     private boolean isMerging() {
3658         return mMergePeer != null || mMergeHost != null;
3659     }
3660 
3661     /**
3662      * Determines if the current call is the host of the merge.
3663      *
3664      * @return {@code true} if the call is the merge host.
3665      */
isMergeHost()3666     private boolean isMergeHost() {
3667         return mMergePeer != null && mMergeHost == null;
3668     }
3669 
3670     /**
3671      * Determines if the current call is the peer of the merge.
3672      *
3673      * @return {@code true} if the call is the merge peer.
3674      */
isMergePeer()3675     private boolean isMergePeer() {
3676         return mMergePeer == null && mMergeHost != null;
3677     }
3678 
3679     /**
3680      * Determines if the call session is pending merge into a conference or not.
3681      *
3682      * @return {@code true} if a merge into a conference is pending, {@code false} otherwise.
3683      */
isCallSessionMergePending()3684     public boolean isCallSessionMergePending() {
3685         return mCallSessionMergePending;
3686     }
3687 
3688     /**
3689      * Sets flag indicating whether the call session is pending merge into a conference or not.
3690      *
3691      * @param callSessionMergePending {@code true} if a merge into the conference is pending,
3692      *      {@code false} otherwise.
3693      */
setCallSessionMergePending(boolean callSessionMergePending)3694     private void setCallSessionMergePending(boolean callSessionMergePending) {
3695         mCallSessionMergePending = callSessionMergePending;
3696     }
3697 
3698     /**
3699      * Determines if there is a conference merge in process.  If there is a merge in process,
3700      * determines if both the merge host and peer sessions have completed the merge process.  This
3701      * means that we have received terminate or hold signals for the sessions, indicating that they
3702      * are no longer in the process of being merged into the conference.
3703      * <p>
3704      * The sessions are considered to have merged if: both calls still have merge peer/host
3705      * relationships configured,  both sessions are not waiting to be merged into the conference,
3706      * and the transient conference session is alive in the case of an initial conference.
3707      *
3708      * @return {@code true} where the host and peer sessions have finished merging into the
3709      *      conference, {@code false} if the merge has not yet completed, and {@code false} if there
3710      *      is no conference merge in progress.
3711      */
shouldProcessConferenceResult()3712     private boolean shouldProcessConferenceResult() {
3713         boolean areMergeTriggersDone = false;
3714 
3715         synchronized (ImsCall.this) {
3716             // if there is a merge going on, then the merge host/peer relationships should have been
3717             // set up.  This works for both the initial conference or merging a call into an
3718             // existing conference.
3719             if (!isMergeHost() && !isMergePeer()) {
3720                 if (CONF_DBG) {
3721                     loge("shouldProcessConferenceResult :: no merge in progress");
3722                 }
3723                 return false;
3724             }
3725 
3726             // There is a merge in progress, so check the sessions to ensure:
3727             // 1. Both calls have completed being merged (or failing to merge) into the conference.
3728             // 2. The transient conference session is alive.
3729             if (isMergeHost()) {
3730                 if (CONF_DBG) {
3731                     logi("shouldProcessConferenceResult :: We are a merge host");
3732                     logi("shouldProcessConferenceResult :: Here is the merge peer=" + mMergePeer);
3733                 }
3734                 areMergeTriggersDone = !isCallSessionMergePending() &&
3735                         !mMergePeer.isCallSessionMergePending();
3736                 if (!isMultiparty()) {
3737                     // Only check the transient session when there is no existing conference
3738                     areMergeTriggersDone &= isSessionAlive(mTransientConferenceSession);
3739                 }
3740             } else if (isMergePeer()) {
3741                 if (CONF_DBG) {
3742                     logi("shouldProcessConferenceResult :: We are a merge peer");
3743                     logi("shouldProcessConferenceResult :: Here is the merge host=" + mMergeHost);
3744                 }
3745                 areMergeTriggersDone = !isCallSessionMergePending() &&
3746                         !mMergeHost.isCallSessionMergePending();
3747                 if (!mMergeHost.isMultiparty()) {
3748                     // Only check the transient session when there is no existing conference
3749                     areMergeTriggersDone &= isSessionAlive(mMergeHost.mTransientConferenceSession);
3750                 } else {
3751                     // This else block is a special case for Verizon to handle these steps
3752                     // 1. Establish a conference call.
3753                     // 2. Add a new call (conference in in BG)
3754                     // 3. Swap (conference active on FG)
3755                     // 4. Merge
3756                     // What happens here is that the BG call gets a terminated callback
3757                     // because it was added to the conference. I've seen where
3758                     // the FG gets no callback at all because its already active.
3759                     // So if we continue to wait for it to set its isCallSessionMerging
3760                     // flag to false...we'll be waiting forever.
3761                     areMergeTriggersDone = !isCallSessionMergePending();
3762                 }
3763             } else {
3764                 // Realistically this shouldn't happen, but best to be safe.
3765                 loge("shouldProcessConferenceResult : merge in progress but call is neither" +
3766                         " host nor peer.");
3767             }
3768             if (CONF_DBG) {
3769                 logi("shouldProcessConferenceResult :: returning:" +
3770                         (areMergeTriggersDone ? "true" : "false"));
3771             }
3772         }
3773         return areMergeTriggersDone;
3774     }
3775 
3776     /**
3777      * Provides a string representation of the {@link ImsCall}.  Primarily intended for use in log
3778      * statements.
3779      *
3780      * @return String representation of call.
3781      */
3782     @Override
toString()3783     public String toString() {
3784         StringBuilder sb = new StringBuilder();
3785         sb.append("[ImsCall objId:");
3786         sb.append(System.identityHashCode(this));
3787         sb.append(" onHold:");
3788         sb.append(isOnHold() ? "Y" : "N");
3789         sb.append(" mute:");
3790         sb.append(isMuted() ? "Y" : "N");
3791         ImsCallProfile imsCallProfile = mCallProfile;
3792         if (imsCallProfile != null) {
3793             sb.append(" mCallProfile:" + imsCallProfile);
3794             sb.append(" networkType:");
3795             sb.append(getNetworkType());
3796         }
3797         sb.append(" updateRequest:");
3798         sb.append(updateRequestToString(mUpdateRequest));
3799         sb.append(" merging:");
3800         sb.append(isMerging() ? "Y" : "N");
3801         if (isMerging()) {
3802             if (isMergePeer()) {
3803                 sb.append("P");
3804             } else {
3805                 sb.append("H");
3806             }
3807         }
3808         sb.append(" merge action pending:");
3809         sb.append(isCallSessionMergePending() ? "Y" : "N");
3810         sb.append(" merged:");
3811         sb.append(isMerged() ? "Y" : "N");
3812         sb.append(" multiParty:");
3813         sb.append(isMultiparty() ? "Y" : "N");
3814         sb.append(" confHost:");
3815         sb.append(isConferenceHost() ? "Y" : "N");
3816         sb.append(" buried term:");
3817         sb.append(mSessionEndDuringMerge ? "Y" : "N");
3818         sb.append(" isVideo: ");
3819         sb.append(isVideoCall() ? "Y" : "N");
3820         sb.append(" wasVideo: ");
3821         sb.append(mWasVideoCall ? "Y" : "N");
3822         sb.append(" isWifi: ");
3823         sb.append(isWifiCall() ? "Y" : "N");
3824         sb.append(" session:");
3825         sb.append(mSession);
3826         sb.append(" transientSession:");
3827         sb.append(mTransientConferenceSession);
3828         sb.append("]");
3829         return sb.toString();
3830     }
3831 
throwImsException(Throwable t, int code)3832     private void throwImsException(Throwable t, int code) throws ImsException {
3833         if (t instanceof ImsException) {
3834             throw (ImsException) t;
3835         } else {
3836             throw new ImsException(String.valueOf(code), t, code);
3837         }
3838     }
3839 
3840     /**
3841      * Append the ImsCall information to the provided string. Usefull for as a logging helper.
3842      * @param s The original string
3843      * @return The original string with {@code ImsCall} information appended to it.
3844      */
appendImsCallInfoToString(String s)3845     private String appendImsCallInfoToString(String s) {
3846         StringBuilder sb = new StringBuilder();
3847         sb.append(s);
3848         sb.append(" ImsCall=");
3849         sb.append(ImsCall.this);
3850         return sb.toString();
3851     }
3852 
3853     /**
3854      * Updates {@link #mWasVideoCall} based on the current {@link ImsCallProfile} for the call.
3855      *
3856      * @param profile The current {@link ImsCallProfile} for the call.
3857      */
trackVideoStateHistory(ImsCallProfile profile)3858     private void trackVideoStateHistory(ImsCallProfile profile) {
3859         mWasVideoCall = mWasVideoCall || ( profile != null && profile.isVideoCall());
3860     }
3861 
3862     /**
3863      * @return {@code true} if this call was a video call at some point in its life span,
3864      *      {@code false} otherwise.
3865      */
wasVideoCall()3866     public boolean wasVideoCall() {
3867         return mWasVideoCall;
3868     }
3869 
3870     /**
3871      * @return {@code true} if this call is a video call, {@code false} otherwise.
3872      */
isVideoCall()3873     public boolean isVideoCall() {
3874         synchronized(mLockObj) {
3875             return mCallProfile != null && mCallProfile.isVideoCall();
3876         }
3877     }
3878 
3879     /**
3880      * Determines if the current call radio access technology is over WIFI.
3881      * Note: This depends on the RIL exposing the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra.
3882      * This method is primarily intended to be used when checking if answering an incoming audio
3883      * call should cause a wifi video call to drop (e.g.
3884      * {@link android.telephony.CarrierConfigManager#
3885      * KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL} is set).
3886      *
3887      * @return {@code true} if the call is over WIFI, {@code false} otherwise.
3888      */
isWifiCall()3889     public boolean isWifiCall() {
3890         synchronized(mLockObj) {
3891             if (mCallProfile == null) {
3892                 return false;
3893             }
3894             return getNetworkType() == TelephonyManager.NETWORK_TYPE_IWLAN;
3895         }
3896     }
3897 
3898     /**
3899      * Determines the network type for the {@link ImsCall}.
3900      * @return The {@link TelephonyManager} {@code NETWORK_TYPE_*} code in use.
3901      */
getNetworkType()3902     public int getNetworkType() {
3903         synchronized(mLockObj) {
3904             if (mCallProfile == null) {
3905                 return ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
3906             }
3907             int networkType = mCallProfile.getCallExtraInt(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE,
3908                     TelephonyManager.NETWORK_TYPE_UNKNOWN);
3909             if (networkType == TelephonyManager.NETWORK_TYPE_UNKNOWN) {
3910                 //  Try to look at old extras to see if the ImsService is using deprecated behavior.
3911                 String oldRatType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE);
3912                 if (TextUtils.isEmpty(oldRatType)) {
3913                     oldRatType = mCallProfile.getCallExtra(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT);
3914                 }
3915                 try {
3916                     int oldRatTypeConverted = Integer.parseInt(oldRatType);
3917                     networkType = ServiceState.rilRadioTechnologyToNetworkType(oldRatTypeConverted);
3918                 } catch (NumberFormatException e) {
3919                     networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
3920                 }
3921             }
3922             return networkType;
3923         }
3924     }
3925 
3926     /**
3927      * Determines if the current call is a cross sim call
3928      * Note: This depends on the RIL exposing the
3929      * {@link ImsCallProfile#EXTRA_IS_CROSS_SIM_CALL} extra.
3930      *
3931      * @return {@code true} if the call is Cross SIM, {@code false} otherwise.
3932      */
isCrossSimCall()3933     public boolean isCrossSimCall() {
3934         synchronized(mLockObj) {
3935             if (mCallProfile == null) {
3936                 return false;
3937             }
3938             return mCallProfile.getCallExtraBoolean(
3939                     ImsCallProfile.EXTRA_IS_CROSS_SIM_CALL,
3940                     false);
3941         }
3942     }
3943 
3944     /**
3945      * Log a string to the radio buffer at the info level.
3946      * @param s The message to log
3947      */
logi(String s)3948     private void logi(String s) {
3949         Log.i(TAG, appendImsCallInfoToString(s));
3950     }
3951 
3952     /**
3953      * Log a string to the radio buffer at the debug level.
3954      * @param s The message to log
3955      */
logd(String s)3956     private void logd(String s) {
3957         Log.d(TAG, appendImsCallInfoToString(s));
3958     }
3959 
3960     /**
3961      * Log a string to the radio buffer at the verbose level.
3962      * @param s The message to log
3963      */
logv(String s)3964     private void logv(String s) {
3965         Log.v(TAG, appendImsCallInfoToString(s));
3966     }
3967 
3968     /**
3969      * Log a string to the radio buffer at the error level.
3970      * @param s The message to log
3971      */
loge(String s)3972     private void loge(String s) {
3973         Log.e(TAG, appendImsCallInfoToString(s));
3974     }
3975 
3976     /**
3977      * Log a string to the radio buffer at the error level with a throwable
3978      * @param s The message to log
3979      * @param t The associated throwable
3980      */
loge(String s, Throwable t)3981     private void loge(String s, Throwable t) {
3982         Log.e(TAG, appendImsCallInfoToString(s), t);
3983     }
3984 }
3985