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