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