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