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.internal.telephony.imsphone;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.content.Context;
23 import android.net.Uri;
24 import android.os.AsyncResult;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.os.Messenger;
31 import android.os.PersistableBundle;
32 import android.os.PowerManager;
33 import android.os.Registrant;
34 import android.os.SystemClock;
35 import android.telecom.VideoProfile;
36 import android.telephony.CarrierConfigManager;
37 import android.telephony.DisconnectCause;
38 import android.telephony.PhoneNumberUtils;
39 import android.telephony.ServiceState;
40 import android.telephony.TelephonyManager;
41 import android.telephony.ims.AudioCodecAttributes;
42 import android.telephony.ims.ImsCallProfile;
43 import android.telephony.ims.ImsReasonInfo;
44 import android.telephony.ims.ImsStreamMediaProfile;
45 import android.telephony.ims.RtpHeaderExtension;
46 import android.telephony.ims.RtpHeaderExtensionType;
47 import android.text.TextUtils;
48 
49 import com.android.ims.ImsCall;
50 import com.android.ims.ImsException;
51 import com.android.ims.internal.ImsVideoCallProviderWrapper;
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.telephony.CallStateException;
54 import com.android.internal.telephony.Connection;
55 import com.android.internal.telephony.Phone;
56 import com.android.internal.telephony.PhoneConstants;
57 import com.android.internal.telephony.UUSInfo;
58 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
59 import com.android.internal.telephony.metrics.TelephonyMetrics;
60 import com.android.telephony.Rlog;
61 
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Collections;
65 import java.util.Objects;
66 import java.util.Set;
67 
68 /**
69  * {@hide}
70  */
71 public class ImsPhoneConnection extends Connection implements
72         ImsVideoCallProviderWrapper.ImsVideoProviderWrapperCallback {
73 
74     private static final String LOG_TAG = "ImsPhoneConnection";
75     private static final boolean DBG = true;
76 
77     //***** Instance Variables
78 
79     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
80     private ImsPhoneCallTracker mOwner;
81     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
82     private ImsPhoneCall mParent;
83     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
84     private ImsCall mImsCall;
85     private final Bundle mExtras = new Bundle();
86     private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
87 
88     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
89     private boolean mDisconnected;
90 
91     /*
92     int mIndex;          // index in ImsPhoneCallTracker.connections[], -1 if unassigned
93                         // The GSM index is 1 + this
94     */
95 
96     /*
97      * These time/timespan values are based on System.currentTimeMillis(),
98      * i.e., "wall clock" time.
99      */
100     private long mDisconnectTime;
101 
102     private UUSInfo mUusInfo;
103     private Handler mHandler;
104     private final Messenger mHandlerMessenger;
105 
106     private PowerManager.WakeLock mPartialWakeLock;
107 
108     // The cached connect time of the connection when it turns into a conference.
109     private long mConferenceConnectTime = 0;
110 
111     // The cached delay to be used between DTMF tones fetched from carrier config.
112     private int mDtmfToneDelay = 0;
113 
114     private boolean mIsEmergency = false;
115 
116     private boolean mIsWpsCall = false;
117 
118     /**
119      * Used to indicate that video state changes detected by
120      * {@link #updateMediaCapabilities(ImsCall)} should be ignored.  When a video state change from
121      * unpaused to paused occurs, we set this flag and then update the existing video state when
122      * new {@link #onReceiveSessionModifyResponse(int, VideoProfile, VideoProfile)} callbacks come
123      * in.  When the video un-pauses we continue receiving the video state updates.
124      */
125     private boolean mShouldIgnoreVideoStateChanges = false;
126 
127     private ImsVideoCallProviderWrapper mImsVideoCallProviderWrapper;
128 
129     private int mPreciseDisconnectCause = 0;
130 
131     private ImsRttTextHandler mRttTextHandler;
132     private android.telecom.Connection.RttTextStream mRttTextStream;
133     // This reflects the RTT status as reported to us by the IMS stack via the media profile.
134     private boolean mIsRttEnabledForCall = false;
135 
136     /**
137      * Used to indicate that this call is in the midst of being merged into a conference.
138      */
139     private boolean mIsMergeInProcess = false;
140 
141     /**
142      * Used as an override to determine whether video is locally available for this call.
143      * This allows video availability to be overridden in the case that the modem says video is
144      * currently available, but mobile data is off and the carrier is metering data for video
145      * calls.
146      */
147     private boolean mIsLocalVideoCapable = true;
148 
149     /**
150      * When the call is in a disconnected, state, will be set to the {@link ImsReasonInfo}
151      * associated with the disconnection, if known.
152      */
153     private ImsReasonInfo mImsReasonInfo;
154 
155     /**
156      * Used to indicate that this call is held by remote party.
157      */
158     private boolean mIsHeldByRemote = false;
159 
160     /**
161      * Used to indicate if both the user and carrier config have enabled the business composer.
162      */
163     private boolean mIsBusinessComposerFeatureEnabled = false;
164 
165     //***** Event Constants
166     private static final int EVENT_DTMF_DONE = 1;
167     private static final int EVENT_PAUSE_DONE = 2;
168     private static final int EVENT_NEXT_POST_DIAL = 3;
169     private static final int EVENT_WAKE_LOCK_TIMEOUT = 4;
170     private static final int EVENT_DTMF_DELAY_DONE = 5;
171 
172     //***** Constants
173     @VisibleForTesting static final int PAUSE_DELAY_MILLIS = 3 * 1000;
174     private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000;
175 
176     //***** Inner Classes
177 
178     class MyHandler extends Handler {
MyHandler(Looper l)179         MyHandler(Looper l) {super(l);}
180 
181         @Override
182         public void
handleMessage(Message msg)183         handleMessage(Message msg) {
184 
185             switch (msg.what) {
186                 case EVENT_NEXT_POST_DIAL:
187                 case EVENT_DTMF_DELAY_DONE:
188                 case EVENT_PAUSE_DONE:
189                     processNextPostDialChar();
190                     break;
191                 case EVENT_WAKE_LOCK_TIMEOUT:
192                     releaseWakeLock();
193                     break;
194                 case EVENT_DTMF_DONE:
195                     // We may need to add a delay specified by carrier between DTMF tones that are
196                     // sent out.
197                     mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE),
198                             mDtmfToneDelay);
199                     break;
200             }
201         }
202     }
203 
204     //***** Constructors
205 
206     /** This is probably an MT call */
ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct, ImsPhoneCall parent, boolean isUnknown)207     public ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct,
208            ImsPhoneCall parent, boolean isUnknown) {
209         super(PhoneConstants.PHONE_TYPE_IMS);
210         createWakeLock(phone.getContext());
211         acquireWakeLock();
212 
213         mOwner = ct;
214         mHandler = new MyHandler(mOwner.getLooper());
215         mHandlerMessenger = new Messenger(mHandler);
216         mImsCall = imsCall;
217         mIsAdhocConference = isMultiparty();
218 
219         if ((imsCall != null) && (imsCall.getCallProfile() != null)) {
220             mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI);
221             mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA);
222             mNumberPresentation = ImsCallProfile.OIRToPresentation(
223                     imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR));
224             mCnapNamePresentation = ImsCallProfile.OIRToPresentation(
225                     imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
226             setNumberVerificationStatus(toTelecomVerificationStatus(
227                     imsCall.getCallProfile().getCallerNumberVerificationStatus()));
228             updateMediaCapabilities(imsCall);
229         } else {
230             mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
231             mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN;
232         }
233 
234         mIsIncoming = !isUnknown;
235         mCreateTime = System.currentTimeMillis();
236         mUusInfo = null;
237 
238         if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
239             setIsBusinessComposerFeatureEnabled(phone);
240         }
241 
242         // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally
243         // in the ImsPhoneConnection.  This isn't going to inform any listeners (since the original
244         // connection is not likely to be associated with a TelephonyConnection yet).
245         updateExtras(imsCall);
246 
247         mParent = parent;
248         mParent.attach(this,
249                 (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING));
250 
251         fetchDtmfToneDelay(phone);
252 
253         if (phone.getContext().getResources().getBoolean(
254                 com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
255             setAudioModeIsVoip(true);
256         }
257     }
258 
259     /** This is an MO call, created when dialing */
ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct, ImsPhoneCall parent, boolean isEmergency, boolean isWpsCall, ImsPhone.ImsDialArgs dialArgs)260     public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct,
261             ImsPhoneCall parent, boolean isEmergency, boolean isWpsCall,
262             ImsPhone.ImsDialArgs dialArgs) {
263         super(PhoneConstants.PHONE_TYPE_IMS);
264         createWakeLock(phone.getContext());
265         acquireWakeLock();
266 
267         mOwner = ct;
268         mHandler = new MyHandler(mOwner.getLooper());
269         mHandlerMessenger = new Messenger(mHandler);
270 
271         mDialString = dialString;
272 
273         mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString);
274         mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString);
275 
276         //mIndex = -1;
277 
278         mIsIncoming = false;
279         mCnapName = null;
280         mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
281         mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
282         mCreateTime = System.currentTimeMillis();
283 
284         mParent = parent;
285         parent.attachFake(this, ImsPhoneCall.State.DIALING);
286 
287         mIsEmergency = isEmergency;
288         if (isEmergency) {
289             setEmergencyCallInfo(mOwner, dialArgs);
290 
291             if (getEmergencyNumberInfo() == null) {
292                 // There was no emergency number info found for this call, however it is
293                 // still marked as an emergency number. This may happen if it was a redialed
294                 // non-detectable emergency call from IMS.
295                 setNonDetectableEmergencyCallInfo(dialArgs.eccCategory, new ArrayList<String>());
296             }
297         }
298 
299         mIsWpsCall = isWpsCall;
300 
301         fetchDtmfToneDelay(phone);
302 
303         if (phone.getContext().getResources().getBoolean(
304                 com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
305             setAudioModeIsVoip(true);
306         }
307     }
308 
309     /** This is an MO conference call, created when dialing */
ImsPhoneConnection(Phone phone, String[] participantsToDial, ImsPhoneCallTracker ct, ImsPhoneCall parent, boolean isEmergency)310     public ImsPhoneConnection(Phone phone, String[] participantsToDial, ImsPhoneCallTracker ct,
311             ImsPhoneCall parent, boolean isEmergency) {
312         super(PhoneConstants.PHONE_TYPE_IMS);
313         createWakeLock(phone.getContext());
314         acquireWakeLock();
315 
316         mOwner = ct;
317         mHandler = new MyHandler(mOwner.getLooper());
318         mHandlerMessenger = new Messenger(mHandler);
319 
320         mDialString = mAddress = Connection.ADHOC_CONFERENCE_ADDRESS;
321         mParticipantsToDial = participantsToDial;
322         mIsAdhocConference = true;
323 
324         mIsIncoming = false;
325         mCnapName = null;
326         mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED;
327         mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED;
328         mCreateTime = System.currentTimeMillis();
329 
330         mParent = parent;
331         parent.attachFake(this, ImsPhoneCall.State.DIALING);
332 
333         if (phone.getContext().getResources().getBoolean(
334                 com.android.internal.R.bool.config_use_voip_mode_for_ims)) {
335             setAudioModeIsVoip(true);
336         }
337     }
338 
339     @VisibleForTesting
setTelephonyMetrics(TelephonyMetrics tm)340     public void setTelephonyMetrics(TelephonyMetrics tm) {
341         mMetrics = tm;
342     }
343 
dispose()344     public void dispose() {
345     }
346 
347     static boolean
equalsHandlesNulls(Object a, Object b)348     equalsHandlesNulls (Object a, Object b) {
349         return (a == null) ? (b == null) : a.equals (b);
350     }
351 
352     static boolean
equalsBaseDialString(String a, String b)353     equalsBaseDialString (String a, String b) {
354         return (a == null) ? (b == null) : (b != null && a.startsWith (b));
355     }
356 
applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities)357     private int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) {
358         Rlog.i(LOG_TAG, "applyLocalCallCapabilities - localProfile = " + localProfile);
359         capabilities = removeCapability(capabilities,
360                 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
361 
362         if (!mIsLocalVideoCapable) {
363             Rlog.i(LOG_TAG, "applyLocalCallCapabilities - disabling video (overidden)");
364             return capabilities;
365         }
366         switch (localProfile.mCallType) {
367             case ImsCallProfile.CALL_TYPE_VT:
368                 // Fall-through
369             case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE:
370                 capabilities = addCapability(capabilities,
371                         Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
372                 break;
373         }
374         return capabilities;
375     }
376 
applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities)377     private static int applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities) {
378         Rlog.w(LOG_TAG, "applyRemoteCallCapabilities - remoteProfile = "+remoteProfile);
379         capabilities = removeCapability(capabilities,
380                 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
381         capabilities = removeCapability(capabilities,
382                 Connection.Capability.SUPPORTS_RTT_REMOTE);
383 
384         switch (remoteProfile.mCallType) {
385             case ImsCallProfile.CALL_TYPE_VT:
386                 // fall-through
387             case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE:
388                 capabilities = addCapability(capabilities,
389                         Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
390                 break;
391         }
392 
393         if (remoteProfile.getMediaProfile().getRttMode() == ImsStreamMediaProfile.RTT_MODE_FULL) {
394             capabilities = addCapability(capabilities, Connection.Capability.SUPPORTS_RTT_REMOTE);
395         }
396         return capabilities;
397     }
398 
399     @Override
getOrigDialString()400     public String getOrigDialString(){
401         return mDialString;
402     }
403 
404     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
405     @Override
getCall()406     public ImsPhoneCall getCall() {
407         return mParent;
408     }
409 
410     @Override
getDisconnectTime()411     public long getDisconnectTime() {
412         return mDisconnectTime;
413     }
414 
415     @Override
getHoldingStartTime()416     public long getHoldingStartTime() {
417         return mHoldingStartTime;
418     }
419 
420     @Override
getHoldDurationMillis()421     public long getHoldDurationMillis() {
422         if (getState() != ImsPhoneCall.State.HOLDING) {
423             // If not holding, return 0
424             return 0;
425         } else {
426             return SystemClock.elapsedRealtime() - mHoldingStartTime;
427         }
428     }
429 
setDisconnectCause(int cause)430     public void setDisconnectCause(int cause) {
431         Rlog.d(LOG_TAG, "setDisconnectCause: cause=" + cause);
432         mCause = cause;
433     }
434 
435     /** Get the disconnect cause for connection*/
getDisconnectCause()436     public int getDisconnectCause() {
437         Rlog.d(LOG_TAG, "getDisconnectCause: cause=" + mCause);
438         return mCause;
439     }
440 
isIncomingCallAutoRejected()441     public boolean isIncomingCallAutoRejected() {
442         return mCause == DisconnectCause.INCOMING_AUTO_REJECTED ? true : false;
443     }
444 
445     @Override
getVendorDisconnectCause()446     public String getVendorDisconnectCause() {
447       return null;
448     }
449 
450     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
getOwner()451     public ImsPhoneCallTracker getOwner () {
452         return mOwner;
453     }
454 
455     @Override
getState()456     public ImsPhoneCall.State getState() {
457         if (mDisconnected) {
458             return ImsPhoneCall.State.DISCONNECTED;
459         } else {
460             return super.getState();
461         }
462     }
463 
464     @Override
deflect(String number)465     public void deflect(String number) throws CallStateException {
466         if (mParent.getState().isRinging()) {
467             try {
468                 if (mImsCall != null) {
469                     mImsCall.deflect(number);
470                 } else {
471                     throw new CallStateException("no valid ims call to deflect");
472                 }
473             } catch (ImsException e) {
474                 throw new CallStateException("cannot deflect call");
475             }
476         } else {
477             throw new CallStateException("phone not ringing");
478         }
479     }
480 
481     @Override
transfer(String number, boolean isConfirmationRequired)482     public void transfer(String number, boolean isConfirmationRequired) throws CallStateException {
483         try {
484             if (mImsCall != null) {
485                 mImsCall.transfer(number, isConfirmationRequired);
486             } else {
487                 throw new CallStateException("no valid ims call to transfer");
488             }
489         } catch (ImsException e) {
490             throw new CallStateException("cannot transfer call");
491         }
492     }
493 
494     @Override
consultativeTransfer(Connection other)495     public void consultativeTransfer(Connection other) throws CallStateException {
496         try {
497             if (mImsCall != null) {
498                 mImsCall.consultativeTransfer(((ImsPhoneConnection) other).getImsCall());
499             } else {
500                 throw new CallStateException("no valid ims call to transfer");
501             }
502         } catch (ImsException e) {
503             throw new CallStateException("cannot transfer call");
504         }
505     }
506 
507     @Override
hangup()508     public void hangup() throws CallStateException {
509         if (!mDisconnected) {
510             mOwner.hangup(this);
511         } else {
512             throw new CallStateException ("disconnected");
513         }
514     }
515 
516     @Override
separate()517     public void separate() throws CallStateException {
518         throw new CallStateException ("not supported");
519     }
520 
521     @Override
proceedAfterWaitChar()522     public void proceedAfterWaitChar() {
523         if (mPostDialState != PostDialState.WAIT) {
524             Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
525                     + "getPostDialState() to be WAIT but was " + mPostDialState);
526             return;
527         }
528 
529         setPostDialState(PostDialState.STARTED);
530 
531         processNextPostDialChar();
532     }
533 
534     @Override
proceedAfterWildChar(String str)535     public void proceedAfterWildChar(String str) {
536         if (mPostDialState != PostDialState.WILD) {
537             Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected "
538                     + "getPostDialState() to be WILD but was " + mPostDialState);
539             return;
540         }
541 
542         setPostDialState(PostDialState.STARTED);
543 
544         // make a new postDialString, with the wild char replacement string
545         // at the beginning, followed by the remaining postDialString.
546 
547         StringBuilder buf = new StringBuilder(str);
548         buf.append(mPostDialString.substring(mNextPostDialChar));
549         mPostDialString = buf.toString();
550         mNextPostDialChar = 0;
551         if (Phone.DEBUG_PHONE) {
552             Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " +
553                     mPostDialString);
554         }
555 
556         processNextPostDialChar();
557     }
558 
559     @Override
cancelPostDial()560     public void cancelPostDial() {
561         setPostDialState(PostDialState.CANCELLED);
562     }
563 
564     /**
565      * Called when this Connection is being hung up locally (eg, user pressed "end")
566      */
onHangupLocal()567     public void onHangupLocal() {
568         mCause = DisconnectCause.LOCAL;
569     }
570 
571     /** Called when the connection has been disconnected */
572     @Override
onDisconnect(int cause)573     public boolean onDisconnect(int cause) {
574         Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
575         if (mCause != DisconnectCause.LOCAL || cause == DisconnectCause.INCOMING_REJECTED) {
576             mCause = cause;
577         }
578         return onDisconnect();
579     }
580 
581     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
onDisconnect()582     public boolean onDisconnect() {
583         boolean changed = false;
584 
585         if (!mDisconnected) {
586             //mIndex = -1;
587 
588             mDisconnectTime = System.currentTimeMillis();
589             mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
590             mDisconnected = true;
591 
592             mOwner.mPhone.notifyDisconnect(this);
593             notifyDisconnect(mCause);
594 
595             if (mParent != null) {
596                 changed = mParent.connectionDisconnected(this);
597             } else {
598                 Rlog.d(LOG_TAG, "onDisconnect: no parent");
599             }
600             synchronized (this) {
601                 if (mRttTextHandler != null) {
602                     mRttTextHandler.tearDown();
603                 }
604                 if (mImsCall != null) mImsCall.close();
605                 mImsCall = null;
606                 if (mImsVideoCallProviderWrapper != null) {
607                     mImsVideoCallProviderWrapper.tearDown();
608                 }
609             }
610         }
611         releaseWakeLock();
612         return changed;
613     }
614 
615     /**
616      * An incoming or outgoing call has connected
617      */
618     void
onConnectedInOrOut()619     onConnectedInOrOut() {
620         mConnectTime = System.currentTimeMillis();
621         mConnectTimeReal = SystemClock.elapsedRealtime();
622         mDuration = 0;
623 
624         if (Phone.DEBUG_PHONE) {
625             Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime);
626         }
627 
628         if (!mIsIncoming) {
629             // outgoing calls only
630             processNextPostDialChar();
631         }
632         releaseWakeLock();
633     }
634 
635     /*package*/ void
onStartedHolding()636     onStartedHolding() {
637         mHoldingStartTime = SystemClock.elapsedRealtime();
638     }
639     /**
640      * Performs the appropriate action for a post-dial char, but does not
641      * notify application. returns false if the character is invalid and
642      * should be ignored
643      */
644     private boolean
processPostDialChar(char c)645     processPostDialChar(char c) {
646         if (PhoneNumberUtils.is12Key(c)) {
647             Message dtmfComplete = mHandler.obtainMessage(EVENT_DTMF_DONE);
648             dtmfComplete.replyTo = mHandlerMessenger;
649             mOwner.sendDtmf(c, dtmfComplete);
650         } else if (c == PhoneNumberUtils.PAUSE) {
651             // From TS 22.101:
652             // It continues...
653             // Upon the called party answering the UE shall send the DTMF digits
654             // automatically to the network after a delay of 3 seconds( 20 ).
655             // The digits shall be sent according to the procedures and timing
656             // specified in 3GPP TS 24.008 [13]. The first occurrence of the
657             // "DTMF Control Digits Separator" shall be used by the ME to
658             // distinguish between the addressing digits (i.e. the phone number)
659             // and the DTMF digits. Upon subsequent occurrences of the
660             // separator,
661             // the UE shall pause again for 3 seconds ( 20 ) before sending
662             // any further DTMF digits.
663             mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
664                     PAUSE_DELAY_MILLIS);
665         } else if (c == PhoneNumberUtils.WAIT) {
666             setPostDialState(PostDialState.WAIT);
667         } else if (c == PhoneNumberUtils.WILD) {
668             setPostDialState(PostDialState.WILD);
669         } else {
670             return false;
671         }
672 
673         return true;
674     }
675 
676     @Override
finalize()677     protected void finalize() {
678         releaseWakeLock();
679     }
680 
681     private void
processNextPostDialChar()682     processNextPostDialChar() {
683         char c = 0;
684         Registrant postDialHandler;
685 
686         if (mPostDialState == PostDialState.CANCELLED) {
687             //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail");
688             return;
689         }
690 
691         if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) {
692             setPostDialState(PostDialState.COMPLETE);
693 
694             // notifyMessage.arg1 is 0 on complete
695             c = 0;
696         } else {
697             boolean isValid;
698 
699             setPostDialState(PostDialState.STARTED);
700 
701             c = mPostDialString.charAt(mNextPostDialChar++);
702 
703             isValid = processPostDialChar(c);
704 
705             if (!isValid) {
706                 // Will call processNextPostDialChar
707                 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
708                 // Don't notify application
709                 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
710                 return;
711             }
712         }
713 
714         notifyPostDialListenersNextChar(c);
715 
716         // TODO: remove the following code since the handler no longer executes anything.
717         postDialHandler = mOwner.mPhone.getPostDialHandler();
718 
719         Message notifyMessage;
720 
721         if (postDialHandler != null
722                 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
723             // The AsyncResult.result is the Connection object
724             PostDialState state = mPostDialState;
725             AsyncResult ar = AsyncResult.forMessage(notifyMessage);
726             ar.result = this;
727             ar.userObj = state;
728 
729             // arg1 is the character that was/is being processed
730             notifyMessage.arg1 = c;
731 
732             //Rlog.v(LOG_TAG,
733             //      "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
734             notifyMessage.sendToTarget();
735         }
736     }
737 
738     /**
739      * Set post dial state and acquire wake lock while switching to "started"
740      * state, the wake lock will be released if state switches out of "started"
741      * state or after WAKE_LOCK_TIMEOUT_MILLIS.
742      * @param s new PostDialState
743      */
setPostDialState(PostDialState s)744     private void setPostDialState(PostDialState s) {
745         if (mPostDialState != PostDialState.STARTED
746                 && s == PostDialState.STARTED) {
747             acquireWakeLock();
748             Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
749             mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
750         } else if (mPostDialState == PostDialState.STARTED
751                 && s != PostDialState.STARTED) {
752             mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
753             releaseWakeLock();
754         }
755         mPostDialState = s;
756         notifyPostDialListeners();
757     }
758 
759     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
760     private void
createWakeLock(Context context)761     createWakeLock(Context context) {
762         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
763         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
764     }
765 
766     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
767     private void
acquireWakeLock()768     acquireWakeLock() {
769         Rlog.d(LOG_TAG, "acquireWakeLock");
770         mPartialWakeLock.acquire();
771     }
772 
773     void
releaseWakeLock()774     releaseWakeLock() {
775         if (mPartialWakeLock != null) {
776             synchronized (mPartialWakeLock) {
777                 if (mPartialWakeLock.isHeld()) {
778                     Rlog.d(LOG_TAG, "releaseWakeLock");
779                     mPartialWakeLock.release();
780                 }
781             }
782         }
783     }
784 
fetchDtmfToneDelay(Phone phone)785     private void fetchDtmfToneDelay(Phone phone) {
786         CarrierConfigManager configMgr = (CarrierConfigManager)
787                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
788         PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId());
789         if (b != null) {
790             mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT);
791         }
792     }
793 
794     @Override
getNumberPresentation()795     public int getNumberPresentation() {
796         return mNumberPresentation;
797     }
798 
799     @Override
getUUSInfo()800     public UUSInfo getUUSInfo() {
801         return mUusInfo;
802     }
803 
804     @Override
getOrigConnection()805     public Connection getOrigConnection() {
806         return null;
807     }
808 
809     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
810     @Override
isMultiparty()811     public synchronized boolean isMultiparty() {
812         return mImsCall != null && mImsCall.isMultiparty();
813     }
814 
815     /**
816      * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the
817      * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this
818      * {@link ImsCall} is a member of a conference hosted on another device.
819      *
820      * @return {@code true} if this call is the origin of the conference call it is a member of,
821      *      {@code false} otherwise.
822      */
823     @Override
isConferenceHost()824     public synchronized boolean isConferenceHost() {
825         return mImsCall != null && mImsCall.isConferenceHost();
826     }
827 
828     @Override
isMemberOfPeerConference()829     public boolean isMemberOfPeerConference() {
830         return !isConferenceHost();
831     }
832 
getImsCall()833     public synchronized ImsCall getImsCall() {
834         return mImsCall;
835     }
836 
setImsCall(ImsCall imsCall)837     public synchronized void setImsCall(ImsCall imsCall) {
838         mImsCall = imsCall;
839     }
840 
changeParent(ImsPhoneCall parent)841     public void changeParent(ImsPhoneCall parent) {
842         mParent = parent;
843     }
844 
845     /**
846      * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been
847      *     changed, and {@code false} otherwise.
848      */
849     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
update(ImsCall imsCall, ImsPhoneCall.State state)850     public boolean update(ImsCall imsCall, ImsPhoneCall.State state) {
851         if (state == ImsPhoneCall.State.ACTIVE) {
852             // If the state of the call is active, but there is a pending request to the RIL to hold
853             // the call, we will skip this update.  This is really a signalling delay or failure
854             // from the RIL, but we will prevent it from going through as we will end up erroneously
855             // making this call active when really it should be on hold.
856             if (imsCall.isPendingHold()) {
857                 Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping");
858                 return false;
859             }
860 
861             if (mParent.getState().isRinging() || mParent.getState().isDialing()) {
862                 onConnectedInOrOut();
863             }
864 
865             if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) {
866                 //mForegroundCall should be IDLE
867                 //when accepting WAITING call
868                 //before accept WAITING call,
869                 //the ACTIVE call should be held ahead
870                 mParent.detach(this);
871                 mParent = mOwner.mForegroundCall;
872                 mParent.attach(this);
873             }
874         } else if (state == ImsPhoneCall.State.HOLDING) {
875             onStartedHolding();
876         }
877 
878         boolean updateParent = mParent.update(this, imsCall, state);
879         boolean updateAddressDisplay = updateAddressDisplay(imsCall);
880         boolean updateMediaCapabilities = updateMediaCapabilities(imsCall);
881         boolean updateExtras = updateExtras(imsCall);
882 
883         return updateParent || updateAddressDisplay || updateMediaCapabilities || updateExtras;
884     }
885 
886     /**
887      * Re-evaluate whether ringback should be playing.
888      */
maybeChangeRingbackState()889     public void maybeChangeRingbackState() {
890         Rlog.i(LOG_TAG, "maybeChangeRingbackState");
891         mParent.maybeChangeRingbackState(mImsCall);
892     }
893 
894     @Override
getPreciseDisconnectCause()895     public int getPreciseDisconnectCause() {
896         return mPreciseDisconnectCause;
897     }
898 
setPreciseDisconnectCause(int cause)899     public void setPreciseDisconnectCause(int cause) {
900         mPreciseDisconnectCause = cause;
901     }
902 
903     /**
904      * Notifies this Connection of a request to disconnect a participant of the conference managed
905      * by the connection.
906      *
907      * @param endpoint the {@link android.net.Uri} of the participant to disconnect.
908      */
909     @Override
onDisconnectConferenceParticipant(Uri endpoint)910     public void onDisconnectConferenceParticipant(Uri endpoint) {
911         ImsCall imsCall = getImsCall();
912         if (imsCall == null) {
913             return;
914         }
915         try {
916             imsCall.removeParticipants(new String[]{endpoint.toString()});
917         } catch (ImsException e) {
918             // No session in place -- no change
919             Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+
920                     "Failed to disconnect endpoint = " + endpoint);
921         }
922     }
923 
924     /**
925      * Sets the conference connect time.  Used when an {@code ImsConference} is created to out of
926      * this phone connection.
927      *
928      * @param conferenceConnectTime The conference connect time.
929      */
setConferenceConnectTime(long conferenceConnectTime)930     public void setConferenceConnectTime(long conferenceConnectTime) {
931         mConferenceConnectTime = conferenceConnectTime;
932     }
933 
934     /**
935      * @return The conference connect time.
936      */
getConferenceConnectTime()937     public long getConferenceConnectTime() {
938         return mConferenceConnectTime;
939     }
940 
941     /**
942      * Check for a change in the address display related fields for the {@link ImsCall}, and
943      * update the {@link ImsPhoneConnection} with this information.
944      *
945      * @param imsCall The call to check for changes in address display fields.
946      * @return Whether the address display fields have been changed.
947      */
updateAddressDisplay(ImsCall imsCall)948     public boolean updateAddressDisplay(ImsCall imsCall) {
949         if (imsCall == null) {
950             return false;
951         }
952 
953         boolean changed = false;
954         ImsCallProfile callProfile = imsCall.getCallProfile();
955         if (callProfile != null && isIncoming()) {
956             // Only look for changes to the address for incoming calls.  The originating identity
957             // can change for outgoing calls due to, for example, a call being forwarded to
958             // voicemail.  This address change does not need to be presented to the user.
959             String address = callProfile.getCallExtra(ImsCallProfile.EXTRA_OI);
960             String name = callProfile.getCallExtra(ImsCallProfile.EXTRA_CNA);
961             int nump = ImsCallProfile.OIRToPresentation(
962                     callProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR));
963             int namep = ImsCallProfile.OIRToPresentation(
964                     callProfile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP));
965             if (Phone.DEBUG_PHONE) {
966                 Rlog.d(LOG_TAG, "updateAddressDisplay: callId = " + getTelecomCallId()
967                         + " address = " + Rlog.pii(LOG_TAG, address) + " name = "
968                         + Rlog.pii(LOG_TAG, name) + " nump = " + nump + " namep = " + namep);
969             }
970             if (!mIsMergeInProcess) {
971                 // Only process changes to the name and address when a merge is not in process.
972                 // When call A initiated a merge with call B to form a conference C, there is a
973                 // point in time when the ImsCall transfers the conference call session into A,
974                 // at which point the ImsConferenceController creates the conference in Telecom.
975                 // For some carriers C will have a unique conference URI address.  Swapping the
976                 // conference session into A, which is about to be disconnected, to be logged to
977                 // the call log using the conference address.  To prevent this we suppress updates
978                 // to the call address while a merge is in process.
979                 if (!equalsBaseDialString(mAddress, address)) {
980                     mAddress = address;
981                     changed = true;
982                 }
983                 if (TextUtils.isEmpty(name)) {
984                     if (!TextUtils.isEmpty(mCnapName)) {
985                         mCnapName = "";
986                         changed = true;
987                     }
988                 } else if (!name.equals(mCnapName)) {
989                     mCnapName = name;
990                     changed = true;
991                 }
992                 if (mNumberPresentation != nump) {
993                     mNumberPresentation = nump;
994                     changed = true;
995                 }
996                 if (mCnapNamePresentation != namep) {
997                     mCnapNamePresentation = namep;
998                     changed = true;
999                 }
1000             }
1001         }
1002         return changed;
1003     }
1004 
1005     /**
1006      * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and
1007      * update the {@link ImsPhoneConnection} with this information.
1008      *
1009      * @param imsCall The call to check for changes in media capabilities.
1010      * @return Whether the media capabilities have been changed.
1011      */
updateMediaCapabilities(ImsCall imsCall)1012     public boolean updateMediaCapabilities(ImsCall imsCall) {
1013         if (imsCall == null) {
1014             return false;
1015         }
1016 
1017         boolean changed = false;
1018 
1019         try {
1020             // The actual call profile (negotiated between local and peer).
1021             ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile();
1022 
1023             if (negotiatedCallProfile != null) {
1024                 int oldVideoState = getVideoState();
1025                 int newVideoState = ImsCallProfile
1026                         .getVideoStateFromImsCallProfile(negotiatedCallProfile);
1027 
1028                 if (oldVideoState != newVideoState) {
1029                     // The video state has changed.  See also code in onReceiveSessionModifyResponse
1030                     // below.  When the video enters a paused state, subsequent changes to the video
1031                     // state will not be reported by the modem.  In onReceiveSessionModifyResponse
1032                     // we will be updating the current video state while paused to include any
1033                     // changes the modem reports via the video provider.  When the video enters an
1034                     // unpaused state, we will resume passing the video states from the modem as is.
1035                     if (VideoProfile.isPaused(oldVideoState) &&
1036                             !VideoProfile.isPaused(newVideoState)) {
1037                         // Video entered un-paused state; recognize updates from now on; we want to
1038                         // ensure that the new un-paused state is propagated to Telecom, so change
1039                         // this now.
1040                         mShouldIgnoreVideoStateChanges = false;
1041                     }
1042 
1043                     if (!mShouldIgnoreVideoStateChanges) {
1044                         updateVideoState(newVideoState);
1045                         changed = true;
1046                     } else {
1047                         Rlog.d(LOG_TAG, "updateMediaCapabilities - ignoring video state change " +
1048                                 "due to paused state.");
1049                     }
1050 
1051                     if (!VideoProfile.isPaused(oldVideoState) &&
1052                             VideoProfile.isPaused(newVideoState)) {
1053                         // Video entered pause state; ignore updates until un-paused.  We do this
1054                         // after setVideoState is called above to ensure Telecom is notified that
1055                         // the device has entered paused state.
1056                         mShouldIgnoreVideoStateChanges = true;
1057                     }
1058                 }
1059 
1060                 if (negotiatedCallProfile.mMediaProfile != null) {
1061                     mIsRttEnabledForCall = negotiatedCallProfile.mMediaProfile.isRttCall();
1062 
1063                     if (mIsRttEnabledForCall && mRttTextHandler == null) {
1064                         Rlog.d(LOG_TAG, "updateMediaCapabilities -- turning RTT on, profile="
1065                                 + negotiatedCallProfile);
1066                         startRttTextProcessing();
1067                         onRttInitiated();
1068                         changed = true;
1069                         mOwner.getPhone().getVoiceCallSessionStats().onRttStarted(this);
1070                     } else if (!mIsRttEnabledForCall && mRttTextHandler != null) {
1071                         Rlog.d(LOG_TAG, "updateMediaCapabilities -- turning RTT off, profile="
1072                                 + negotiatedCallProfile);
1073                         mRttTextHandler.tearDown();
1074                         mRttTextHandler = null;
1075                         mRttTextStream = null;
1076                         onRttTerminated();
1077                         changed = true;
1078                     }
1079                 }
1080             }
1081 
1082             // Check for a change in the capabilities for the call and update
1083             // {@link ImsPhoneConnection} with this information.
1084             int capabilities = getConnectionCapabilities();
1085 
1086             // Use carrier config to determine if downgrading directly to audio-only is supported.
1087             if (mOwner.isCarrierDowngradeOfVtCallSupported()) {
1088                 capabilities = addCapability(capabilities,
1089                         Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE |
1090                                 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL);
1091             } else {
1092                 capabilities = removeCapability(capabilities,
1093                         Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE |
1094                                 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL);
1095             }
1096 
1097             // Get the current local call capabilities which might be voice or video or both.
1098             ImsCallProfile localCallProfile = imsCall.getLocalCallProfile();
1099             Rlog.v(LOG_TAG, "update localCallProfile=" + localCallProfile);
1100             if (localCallProfile != null) {
1101                 capabilities = applyLocalCallCapabilities(localCallProfile, capabilities);
1102             }
1103 
1104             // Get the current remote call capabilities which might be voice or video or both.
1105             ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile();
1106             Rlog.v(LOG_TAG, "update remoteCallProfile=" + remoteCallProfile);
1107             if (remoteCallProfile != null) {
1108                 capabilities = applyRemoteCallCapabilities(remoteCallProfile, capabilities);
1109             }
1110             if (getConnectionCapabilities() != capabilities) {
1111                 setConnectionCapabilities(capabilities);
1112                 changed = true;
1113             }
1114 
1115             if (!mOwner.isViLteDataMetered()) {
1116                 Rlog.v(LOG_TAG, "data is not metered");
1117             } else {
1118                 if (mImsVideoCallProviderWrapper != null) {
1119                     mImsVideoCallProviderWrapper.setIsVideoEnabled(
1120                             hasCapabilities(Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
1121                 }
1122             }
1123 
1124             boolean mediaAttributesChanged = false;
1125 
1126             // Metrics for audio codec
1127             if (localCallProfile != null
1128                     && localCallProfile.mMediaProfile.mAudioQuality != mAudioCodec) {
1129                 mAudioCodec = localCallProfile.mMediaProfile.mAudioQuality;
1130                 mMetrics.writeAudioCodecIms(mOwner.mPhone.getPhoneId(), imsCall.getCallSession());
1131                 mOwner.getPhone().getVoiceCallSessionStats().onAudioCodecChanged(this, mAudioCodec);
1132                 changed = true;
1133                 mediaAttributesChanged = true;
1134             }
1135 
1136             if (localCallProfile != null
1137                     && localCallProfile.mMediaProfile.getAudioCodecAttributes() != null) {
1138                 AudioCodecAttributes audioCodecAttributes =
1139                         localCallProfile.mMediaProfile.getAudioCodecAttributes();
1140 
1141                 if (Math.abs(mAudioCodecBitrateKbps
1142                         - audioCodecAttributes.getBitrateRangeKbps().getUpper()) > THRESHOLD) {
1143                     mAudioCodecBitrateKbps = audioCodecAttributes.getBitrateRangeKbps().getUpper();
1144                     changed = true;
1145                     mediaAttributesChanged = true;
1146                 }
1147                 if (Math.abs(mAudioCodecBandwidthKhz
1148                         - audioCodecAttributes.getBandwidthRangeKhz().getUpper()) > THRESHOLD) {
1149                     mAudioCodecBandwidthKhz =
1150                             audioCodecAttributes.getBandwidthRangeKhz().getUpper();
1151                     changed = true;
1152                     mediaAttributesChanged = true;
1153                 }
1154             }
1155 
1156             if (mediaAttributesChanged) {
1157                 Rlog.i(LOG_TAG, "updateMediaCapabilities: mediate attributes changed: codec = "
1158                         + mAudioCodec + ", bitRate=" + mAudioCodecBitrateKbps + ", bandwidth="
1159                         + mAudioCodecBandwidthKhz);
1160                 notifyMediaAttributesChanged();
1161             }
1162 
1163             int newAudioQuality =
1164                     getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile);
1165             if (getAudioQuality() != newAudioQuality) {
1166                 setAudioQuality(newAudioQuality);
1167                 changed = true;
1168             }
1169         } catch (ImsException e) {
1170             // No session in place -- no change
1171         }
1172 
1173         return changed;
1174     }
1175 
updateVideoState(int newVideoState)1176     private void updateVideoState(int newVideoState) {
1177         if (mImsVideoCallProviderWrapper != null) {
1178             mImsVideoCallProviderWrapper.onVideoStateChanged(newVideoState);
1179         }
1180         setVideoState(newVideoState);
1181         mOwner.getPhone().getVoiceCallSessionStats().onVideoStateChange(this, newVideoState);
1182     }
1183 
1184 
1185     /**
1186      * Send a RTT upgrade request to the remote party.
1187      * @param textStream RTT text stream to use
1188      */
startRtt(android.telecom.Connection.RttTextStream textStream)1189     public void startRtt(android.telecom.Connection.RttTextStream textStream) {
1190         ImsCall imsCall = getImsCall();
1191         if (imsCall == null) {
1192             Rlog.w(LOG_TAG, "startRtt failed, imsCall is null");
1193             return;
1194         }
1195         imsCall.sendRttModifyRequest(true);
1196         setCurrentRttTextStream(textStream);
1197     }
1198 
1199     /**
1200      * Terminate the current RTT session.
1201      */
stopRtt()1202     public void stopRtt() {
1203         ImsCall imsCall = getImsCall();
1204         if (imsCall == null) {
1205             Rlog.w(LOG_TAG, "stopRtt failed, imsCall is null");
1206             return;
1207         }
1208         imsCall.sendRttModifyRequest(false);
1209     }
1210 
1211     /**
1212      * Sends the user's response to a remotely-issued RTT upgrade request
1213      *
1214      * @param textStream A valid {@link android.telecom.Connection.RttTextStream} if the user
1215      *                   accepts, {@code null} if not.
1216      */
sendRttModifyResponse(android.telecom.Connection.RttTextStream textStream)1217     public void sendRttModifyResponse(android.telecom.Connection.RttTextStream textStream) {
1218         boolean accept = textStream != null;
1219         ImsCall imsCall = getImsCall();
1220         if (imsCall == null) {
1221             Rlog.w(LOG_TAG, "sendRttModifyResponse failed, imsCall is null");
1222             return;
1223         }
1224         imsCall.sendRttModifyResponse(accept);
1225         if (accept) {
1226             setCurrentRttTextStream(textStream);
1227         } else {
1228             Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections");
1229         }
1230     }
1231 
onRttMessageReceived(String message)1232     public void onRttMessageReceived(String message) {
1233         synchronized (this) {
1234             if (mRttTextHandler == null) {
1235                 Rlog.w(LOG_TAG, "onRttMessageReceived: RTT text handler not available."
1236                         + " Attempting to create one.");
1237                 if (mRttTextStream == null) {
1238                     Rlog.e(LOG_TAG, "onRttMessageReceived:"
1239                             + " Unable to process incoming message. No textstream available");
1240                     return;
1241                 }
1242                 createRttTextHandler();
1243             }
1244         }
1245         mRttTextHandler.sendToInCall(message);
1246     }
1247 
onRttAudioIndicatorChanged(ImsStreamMediaProfile profile)1248     public void onRttAudioIndicatorChanged(ImsStreamMediaProfile profile) {
1249         Bundle extras = new Bundle();
1250         extras.putBoolean(android.telecom.Connection.EXTRA_IS_RTT_AUDIO_PRESENT,
1251                 profile.isReceivingRttAudio());
1252         onConnectionEvent(android.telecom.Connection.EVENT_RTT_AUDIO_INDICATION_CHANGED,
1253                 extras);
1254     }
1255 
setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream)1256     public void setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream) {
1257         synchronized (this) {
1258             mRttTextStream = rttTextStream;
1259             if (mRttTextHandler == null && mIsRttEnabledForCall) {
1260                 Rlog.i(LOG_TAG, "setCurrentRttTextStream: Creating a text handler");
1261                 createRttTextHandler();
1262             }
1263         }
1264     }
1265 
1266     /**
1267      * Get the corresponding EmergencyNumberTracker associated with the connection.
1268      * @return the EmergencyNumberTracker
1269      */
getEmergencyNumberTracker()1270     public EmergencyNumberTracker getEmergencyNumberTracker() {
1271         if (mOwner != null) {
1272             Phone phone = mOwner.getPhone();
1273             if (phone != null) {
1274                 return phone.getEmergencyNumberTracker();
1275             }
1276         }
1277         return null;
1278     }
1279 
hasRttTextStream()1280     public boolean hasRttTextStream() {
1281         return mRttTextStream != null;
1282     }
1283 
isRttEnabledForCall()1284     public boolean isRttEnabledForCall() {
1285         return mIsRttEnabledForCall;
1286     }
1287 
startRttTextProcessing()1288     public void startRttTextProcessing() {
1289         synchronized (this) {
1290             if (mRttTextStream == null) {
1291                 Rlog.w(LOG_TAG, "startRttTextProcessing: no RTT text stream. Ignoring.");
1292                 return;
1293             }
1294             if (mRttTextHandler != null) {
1295                 Rlog.w(LOG_TAG, "startRttTextProcessing: RTT text handler already exists");
1296                 return;
1297             }
1298             createRttTextHandler();
1299         }
1300     }
1301 
1302     // Make sure to synchronize on ImsPhoneConnection.this before calling.
createRttTextHandler()1303     private void createRttTextHandler() {
1304         mRttTextHandler = new ImsRttTextHandler(Looper.getMainLooper(),
1305                 (message) -> {
1306                     ImsCall imsCall = getImsCall();
1307                     if (imsCall != null) {
1308                         imsCall.sendRttMessage(message);
1309                     } else {
1310                         Rlog.w(LOG_TAG, "createRttTextHandler: imsCall is null");
1311                     }
1312                 });
1313         mRttTextHandler.initialize(mRttTextStream);
1314     }
1315 
1316     /**
1317      * Updates the IMS call rat based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}.
1318      *
1319      * @param extras The ImsCallProfile extras.
1320      */
updateImsCallRatFromExtras(Bundle extras)1321     private void updateImsCallRatFromExtras(Bundle extras) {
1322         if (extras == null) {
1323             return;
1324         }
1325         if (extras.containsKey(ImsCallProfile.EXTRA_CALL_NETWORK_TYPE)
1326                 || extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE)
1327                 || extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) {
1328 
1329             ImsCall call = getImsCall();
1330             int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
1331             if (call != null) {
1332                 networkType = call.getNetworkType();
1333             }
1334 
1335             // Report any changes for network type change
1336             setCallRadioTech(ServiceState.networkTypeToRilRadioTechnology(networkType));
1337         }
1338     }
1339 
updateEmergencyCallFromExtras(Bundle extras)1340     private void updateEmergencyCallFromExtras(Bundle extras) {
1341         if (extras == null) {
1342             return;
1343         }
1344         if (extras.getBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL)) {
1345             setIsNetworkIdentifiedEmergencyCall(true);
1346         }
1347     }
1348 
updateForwardedNumberFromExtras(Bundle extras)1349     private void updateForwardedNumberFromExtras(Bundle extras) {
1350         if (extras == null) {
1351             return;
1352         }
1353         if (extras.containsKey(ImsCallProfile.EXTRA_FORWARDED_NUMBER)) {
1354             String[] forwardedNumberArray =
1355                     extras.getStringArray(ImsCallProfile.EXTRA_FORWARDED_NUMBER);
1356             if (forwardedNumberArray != null) {
1357                 mForwardedNumber = new ArrayList<String>(Arrays.asList(forwardedNumberArray));
1358             }
1359         }
1360     }
1361 
1362     /**
1363      * Check for a change in call extras of {@link ImsCall}, and
1364      * update the {@link ImsPhoneConnection} accordingly.
1365      *
1366      * @param imsCall The call to check for changes in extras.
1367      * @return Whether the extras fields have been changed.
1368      */
updateExtras(ImsCall imsCall)1369     boolean updateExtras(ImsCall imsCall) {
1370         if (imsCall == null) {
1371             return false;
1372         }
1373         final ImsCallProfile callProfile = imsCall.getCallProfile();
1374         final Bundle extras = callProfile != null ? callProfile.mCallExtras : null;
1375         if (extras == null && DBG) {
1376             Rlog.d(LOG_TAG, "Call profile extras are null.");
1377         }
1378         final boolean changed = !areBundlesEqual(extras, mExtras);
1379         if (changed) {
1380             updateImsCallRatFromExtras(extras);
1381             updateEmergencyCallFromExtras(extras);
1382             updateForwardedNumberFromExtras(extras);
1383             mExtras.clear();
1384             if (extras != null) {
1385                 mExtras.putAll(extras);
1386             }
1387             if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
1388                 maybeInjectBusinessComposerExtras(mExtras);
1389             }
1390             setConnectionExtras(mExtras);
1391         }
1392         return changed;
1393     }
1394 
1395     /**
1396      * The Ims Vendor is responsible for setting the ImsCallProfile business call composer
1397      * values (ImsCallProfile.EXTRA_IS_BUSINESS_CALL and
1398      * ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME). This helper notifies Telecom of the business
1399      * composer values which will then be injected into the android.telecom.Call object.
1400      */
1401     @VisibleForTesting
maybeInjectBusinessComposerExtras(Bundle extras)1402     public void maybeInjectBusinessComposerExtras(Bundle extras) {
1403         if (extras == null) {
1404             return;
1405         }
1406         // Telephony should check that the business composer features is on BEFORE
1407         // propagating the business call extras.  This prevents the user from getting
1408         // business call info when they turned the feature off.
1409         if (!mIsBusinessComposerFeatureEnabled) {
1410             Rlog.i(LOG_TAG, "mIBCE: business composer feature is NOT enabled");
1411             return;
1412         }
1413         try {
1414             if (extras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL)
1415                     && !extras.containsKey(android.telecom.Call.EXTRA_IS_BUSINESS_CALL)) {
1416                 boolean v = extras.getBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL);
1417                 Rlog.i(LOG_TAG, String.format("mIBCE: EXTRA_IS_BUSINESS_CALL=[%s]", v));
1418                 extras.putBoolean(android.telecom.Call.EXTRA_IS_BUSINESS_CALL, v);
1419             }
1420 
1421             if (extras.containsKey(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME)
1422                     && !extras.containsKey(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME)) {
1423                 String v = extras.getString(ImsCallProfile.EXTRA_ASSERTED_DISPLAY_NAME);
1424                 Rlog.i(LOG_TAG, String.format("mIBCE: ASSERTED_DISPLAY_NAME=[%s]", v));
1425                 extras.putString(android.telecom.Call.EXTRA_ASSERTED_DISPLAY_NAME, v);
1426             }
1427 
1428         } catch (Exception e) {
1429             e.printStackTrace();
1430         }
1431     }
1432 
1433     @VisibleForTesting
getIsBusinessComposerFeatureEnabled()1434     public boolean getIsBusinessComposerFeatureEnabled() {
1435         return mIsBusinessComposerFeatureEnabled;
1436     }
1437 
1438     @VisibleForTesting
setIsBusinessComposerFeatureEnabled(Phone phone)1439     public void setIsBusinessComposerFeatureEnabled(Phone phone) {
1440         mIsBusinessComposerFeatureEnabled = isBusinessComposerEnabledByConfig(phone)
1441                 && isBusinessOnlyCallComposerEnabledByUser(phone);
1442         Rlog.i(LOG_TAG, String.format(
1443                 "setIsBusinessComposerFeatureEnabled:  mIsBusinessComposerFeatureEnabled=[%b], "
1444                         + "phone=[%s]", mIsBusinessComposerFeatureEnabled, phone));
1445     }
1446 
1447     /**
1448      * Returns whether the carrier supports and has enabled the business composer
1449      */
1450     @VisibleForTesting
isBusinessComposerEnabledByConfig(Phone phone)1451     public boolean isBusinessComposerEnabledByConfig(Phone phone) {
1452         PersistableBundle b = null;
1453         CarrierConfigManager configMgr = phone.getContext().getSystemService(
1454                 CarrierConfigManager.class);
1455 
1456         if (configMgr != null) {
1457             // If an invalid subId is used, this bundle will contain default values.
1458             b = configMgr.getConfigForSubId(phone.getSubId());
1459         }
1460         if (b != null) {
1461             return b.getBoolean(CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL);
1462         } else {
1463             // Return static default defined in CarrierConfigManager.
1464             return CarrierConfigManager.getDefaultConfig()
1465                     .getBoolean(CarrierConfigManager.KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL);
1466         }
1467     }
1468 
1469     /**
1470      * Returns whether the user has enabled the business composer
1471      */
1472     @VisibleForTesting
isBusinessOnlyCallComposerEnabledByUser(Phone phone)1473     public boolean isBusinessOnlyCallComposerEnabledByUser(Phone phone) {
1474         if (phone == null || phone.getContext() == null) {
1475             return false;
1476         }
1477         TelephonyManager tm = (TelephonyManager)
1478                 phone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
1479         if (tm == null) {
1480             Rlog.e(LOG_TAG, "isBusinessOnlyCallComposerEnabledByUser: TelephonyManager is null");
1481             return false;
1482         }
1483         return tm.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_BUSINESS_ONLY
1484                 || tm.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON;
1485     }
1486 
areBundlesEqual(Bundle extras, Bundle newExtras)1487     private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) {
1488         if (extras == null || newExtras == null) {
1489             return extras == newExtras;
1490         }
1491 
1492         if (extras.size() != newExtras.size()) {
1493             return false;
1494         }
1495 
1496         for(String key : extras.keySet()) {
1497             if (key != null) {
1498                 final Object value = extras.get(key);
1499                 final Object newValue = newExtras.get(key);
1500                 if (!Objects.equals(value, newValue)) {
1501                     return false;
1502                 }
1503             }
1504         }
1505         return true;
1506     }
1507 
1508     /**
1509      * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote
1510      * {@link ImsCallProfile}. Indicate a HD audio call if the local stream profile
1511      * is AMR_WB, EVRC_WB, EVS_WB, EVS_SWB, EVS_FB and
1512      * there is no remote restrict cause.
1513      *
1514      * @param localCallProfile The local call profile.
1515      * @param remoteCallProfile The remote call profile.
1516      * @return The audio quality.
1517      */
getAudioQualityFromCallProfile( ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile)1518     private int getAudioQualityFromCallProfile(
1519             ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) {
1520         if (localCallProfile == null || remoteCallProfile == null
1521                 || localCallProfile.mMediaProfile == null) {
1522             return AUDIO_QUALITY_STANDARD;
1523         }
1524 
1525         final boolean isEvsCodecHighDef = (localCallProfile.mMediaProfile.mAudioQuality
1526                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB
1527                 || localCallProfile.mMediaProfile.mAudioQuality
1528                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB
1529                 || localCallProfile.mMediaProfile.mAudioQuality
1530                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB);
1531 
1532         final boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality
1533                         == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB
1534                 || localCallProfile.mMediaProfile.mAudioQuality
1535                         == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB
1536                 || isEvsCodecHighDef)
1537                 && remoteCallProfile.getRestrictCause() == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE;
1538         return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD;
1539     }
1540 
1541     /**
1542      * Provides a string representation of the {@link ImsPhoneConnection}.  Primarily intended for
1543      * use in log statements.
1544      *
1545      * @return String representation of call.
1546      */
1547     @Override
toString()1548     public String toString() {
1549         StringBuilder sb = new StringBuilder();
1550         sb.append("[ImsPhoneConnection objId: ");
1551         sb.append(System.identityHashCode(this));
1552         sb.append(" telecomCallID: ");
1553         sb.append(getTelecomCallId());
1554         sb.append(" address: ");
1555         sb.append(Rlog.pii(LOG_TAG, getAddress()));
1556         sb.append(" isAdhocConf: ");
1557         sb.append(isAdhocConference() ? "Y" : "N");
1558         sb.append(" ImsCall: ");
1559         synchronized (this) {
1560             if (mImsCall == null) {
1561                 sb.append("null");
1562             } else {
1563                 sb.append(mImsCall);
1564             }
1565         }
1566         sb.append("]");
1567         return sb.toString();
1568     }
1569 
1570     @Override
setVideoProvider(android.telecom.Connection.VideoProvider videoProvider)1571     public void setVideoProvider(android.telecom.Connection.VideoProvider videoProvider) {
1572         super.setVideoProvider(videoProvider);
1573 
1574         if (videoProvider instanceof ImsVideoCallProviderWrapper) {
1575             mImsVideoCallProviderWrapper = (ImsVideoCallProviderWrapper) videoProvider;
1576         }
1577     }
1578 
1579     /**
1580      * Indicates whether current phone connection is emergency or not
1581      * @return boolean: true if emergency, false otherwise
1582      */
isEmergency()1583     protected boolean isEmergency() {
1584         return mIsEmergency;
1585     }
1586 
isWpsCall()1587     protected boolean isWpsCall() {
1588         return mIsWpsCall;
1589     }
1590 
1591     /**
1592      * Indicates whether current phone connection is cross sim calling or not
1593      * @return boolean: true if cross sim calling, false otherwise
1594      */
isCrossSimCall()1595     public boolean isCrossSimCall() {
1596         if (mImsCall == null) {
1597             return mExtras.getBoolean(ImsCallProfile.EXTRA_IS_CROSS_SIM_CALL);
1598         }
1599         return mImsCall.isCrossSimCall();
1600     }
1601 
1602     /**
1603      * Handles notifications from the {@link ImsVideoCallProviderWrapper} of session modification
1604      * responses received.
1605      *
1606      * @param status The status of the original request.
1607      * @param requestProfile The requested video profile.
1608      * @param responseProfile The response upon video profile.
1609      */
1610     @Override
onReceiveSessionModifyResponse(int status, VideoProfile requestProfile, VideoProfile responseProfile)1611     public void onReceiveSessionModifyResponse(int status, VideoProfile requestProfile,
1612             VideoProfile responseProfile) {
1613         if (status == android.telecom.Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS &&
1614                 mShouldIgnoreVideoStateChanges) {
1615             int currentVideoState = getVideoState();
1616             int newVideoState = responseProfile.getVideoState();
1617 
1618             // If the current video state is paused, the modem will not send us any changes to
1619             // the TX and RX bits of the video state.  Until the video is un-paused we will
1620             // "fake out" the video state by applying the changes that the modem reports via a
1621             // response.
1622 
1623             // First, find out whether there was a change to the TX or RX bits:
1624             int changedBits = currentVideoState ^ newVideoState;
1625             changedBits &= VideoProfile.STATE_BIDIRECTIONAL;
1626             if (changedBits == 0) {
1627                 // No applicable change, bail out.
1628                 return;
1629             }
1630 
1631             // Turn off any existing bits that changed.
1632             currentVideoState &= ~(changedBits & currentVideoState);
1633             // Turn on any new bits that turned on.
1634             currentVideoState |= changedBits & newVideoState;
1635 
1636             Rlog.d(LOG_TAG, "onReceiveSessionModifyResponse : received " +
1637                     VideoProfile.videoStateToString(requestProfile.getVideoState()) +
1638                     " / " +
1639                     VideoProfile.videoStateToString(responseProfile.getVideoState()) +
1640                     " while paused ; sending new videoState = " +
1641                     VideoProfile.videoStateToString(currentVideoState));
1642             setVideoState(currentVideoState);
1643         }
1644     }
1645 
1646     /**
1647      * Issues a request to pause the video using {@link VideoProfile#STATE_PAUSED} from a source
1648      * other than the InCall UI.
1649      *
1650      * @param source The source of the pause request.
1651      */
pauseVideo(int source)1652     public void pauseVideo(int source) {
1653         if (mImsVideoCallProviderWrapper == null) {
1654             return;
1655         }
1656 
1657         mImsVideoCallProviderWrapper.pauseVideo(getVideoState(), source);
1658     }
1659 
1660     /**
1661      * Issues a request to resume the video using {@link VideoProfile#STATE_PAUSED} from a source
1662      * other than the InCall UI.
1663      *
1664      * @param source The source of the resume request.
1665      */
resumeVideo(int source)1666     public void resumeVideo(int source) {
1667         if (mImsVideoCallProviderWrapper == null) {
1668             return;
1669         }
1670 
1671         mImsVideoCallProviderWrapper.resumeVideo(getVideoState(), source);
1672     }
1673 
1674     /**
1675      * Determines if a specified source has issued a pause request.
1676      *
1677      * @param source The source.
1678      * @return {@code true} if the source issued a pause request, {@code false} otherwise.
1679      */
wasVideoPausedFromSource(int source)1680     public boolean wasVideoPausedFromSource(int source) {
1681         if (mImsVideoCallProviderWrapper == null) {
1682             return false;
1683         }
1684 
1685         return mImsVideoCallProviderWrapper.wasVideoPausedFromSource(source);
1686     }
1687 
1688     /**
1689      * Mark the call as in the process of being merged and inform the UI of the merge start.
1690      */
handleMergeStart()1691     public void handleMergeStart() {
1692         mIsMergeInProcess = true;
1693         onConnectionEvent(android.telecom.Connection.EVENT_MERGE_START, null);
1694     }
1695 
1696     /**
1697      * Mark the call as done merging and inform the UI of the merge start.
1698      */
handleMergeComplete()1699     public void handleMergeComplete() {
1700         mIsMergeInProcess = false;
1701     }
1702 
1703     /**
1704      * Mark the call is held by remote party and inform to the UI.
1705      */
setRemotelyHeld()1706     public void setRemotelyHeld() {
1707         mIsHeldByRemote = true;
1708         onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_HELD, null);
1709     }
1710 
1711     /**
1712      * Mark the call is Unheld by remote party and inform to the UI.
1713      */
setRemotelyUnheld()1714     public void setRemotelyUnheld() {
1715         mIsHeldByRemote = false;
1716         onConnectionEvent(android.telecom.Connection.EVENT_CALL_REMOTELY_UNHELD, null);
1717     }
1718 
1719     /**
1720      * @return whether the remote party is holding the call.
1721      */
isHeldByRemote()1722     public boolean isHeldByRemote() {
1723         Rlog.i(LOG_TAG, "isHeldByRemote=" + mIsHeldByRemote);
1724         return mIsHeldByRemote;
1725     }
1726 
changeToPausedState()1727     public void changeToPausedState() {
1728         int newVideoState = getVideoState() | VideoProfile.STATE_PAUSED;
1729         Rlog.i(LOG_TAG, "ImsPhoneConnection: changeToPausedState - setting paused bit; "
1730                 + "newVideoState=" + VideoProfile.videoStateToString(newVideoState));
1731         updateVideoState(newVideoState);
1732         mShouldIgnoreVideoStateChanges = true;
1733     }
1734 
changeToUnPausedState()1735     public void changeToUnPausedState() {
1736         int newVideoState = getVideoState() & ~VideoProfile.STATE_PAUSED;
1737         Rlog.i(LOG_TAG, "ImsPhoneConnection: changeToUnPausedState - unsetting paused bit; "
1738                 + "newVideoState=" + VideoProfile.videoStateToString(newVideoState));
1739         updateVideoState(newVideoState);
1740         mShouldIgnoreVideoStateChanges = false;
1741     }
1742 
setLocalVideoCapable(boolean isVideoEnabled)1743     public void setLocalVideoCapable(boolean isVideoEnabled) {
1744         mIsLocalVideoCapable = isVideoEnabled;
1745         Rlog.i(LOG_TAG, "setLocalVideoCapable: mIsLocalVideoCapable = " + mIsLocalVideoCapable
1746                 + "; updating local video availability.");
1747         updateMediaCapabilities(getImsCall());
1748     }
1749 
1750     /**
1751      * Sends RTP header extension data.
1752      * @param rtpHeaderExtensions the RTP header extension data to send.
1753      */
sendRtpHeaderExtensions(@onNull Set<RtpHeaderExtension> rtpHeaderExtensions)1754     public void sendRtpHeaderExtensions(@NonNull Set<RtpHeaderExtension> rtpHeaderExtensions) {
1755         if (mImsCall == null) {
1756             Rlog.w(LOG_TAG, "sendRtpHeaderExtensions: Not an IMS call");
1757             return;
1758         }
1759         Rlog.i(LOG_TAG, "sendRtpHeaderExtensions: numExtensions = " + rtpHeaderExtensions.size());
1760         mImsCall.sendRtpHeaderExtensions(rtpHeaderExtensions);
1761     }
1762 
1763     /**
1764      * @return the RTP header extensions accepted for this call.
1765      */
getAcceptedRtpHeaderExtensions()1766     public Set<RtpHeaderExtensionType> getAcceptedRtpHeaderExtensions() {
1767         if (mImsCall == null || mImsCall.getCallProfile() == null) {
1768             return Collections.EMPTY_SET;
1769         }
1770         return mImsCall.getCallProfile().getAcceptedRtpHeaderExtensionTypes();
1771     }
1772 
1773     /**
1774      * For a connection being disconnected, sets the {@link ImsReasonInfo} which describes the
1775      * reason for the disconnection.
1776      * @param imsReasonInfo The IMS reason info.
1777      */
setImsReasonInfo(@ullable ImsReasonInfo imsReasonInfo)1778     public void setImsReasonInfo(@Nullable ImsReasonInfo imsReasonInfo) {
1779         mImsReasonInfo = imsReasonInfo;
1780     }
1781 
1782     /**
1783      * @return the {@link ImsReasonInfo} describing why this connection disconnected, or
1784      * {@code null} otherwise.
1785      */
getImsReasonInfo()1786     public @Nullable ImsReasonInfo getImsReasonInfo() {
1787         return mImsReasonInfo;
1788     }
1789 
1790     /**
1791      * Converts an {@link ImsCallProfile} verification status to a
1792      * {@link android.telecom.Connection} verification status.
1793      * @param verificationStatus The {@link ImsCallProfile} verification status.
1794      * @return The telecom verification status.
1795      */
toTelecomVerificationStatus( @msCallProfile.VerificationStatus int verificationStatus)1796     public static @android.telecom.Connection.VerificationStatus int toTelecomVerificationStatus(
1797             @ImsCallProfile.VerificationStatus int verificationStatus) {
1798         switch (verificationStatus) {
1799             case ImsCallProfile.VERIFICATION_STATUS_PASSED:
1800                 return android.telecom.Connection.VERIFICATION_STATUS_PASSED;
1801             case ImsCallProfile.VERIFICATION_STATUS_FAILED:
1802                 return android.telecom.Connection.VERIFICATION_STATUS_FAILED;
1803             case ImsCallProfile.VERIFICATION_STATUS_NOT_VERIFIED:
1804                 // fall through on purpose
1805             default:
1806                 return android.telecom.Connection.VERIFICATION_STATUS_NOT_VERIFIED;
1807         }
1808     }
1809 
1810     /**
1811      * The priority of the call to the user. A higher number means higher priority.
1812      */
getCallPriority()1813     protected int getCallPriority() {
1814         if (isEmergency()) {
1815             return 2;
1816         } else if (isWpsCall()) {
1817             return 1;
1818         }
1819         return 0;
1820     }
1821 }
1822