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