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