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