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