Home | History | Annotate | Download | only in imsphone
      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 {
    140         MyHandler(Looper l) {super(l);}
    141 
    142         @Override
    143         public void
    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 */
    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 */
    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 
    249     public void dispose() {
    250     }
    251 
    252     static boolean
    253     equalsHandlesNulls (Object a, Object b) {
    254         return (a == null) ? (b == null) : a.equals (b);
    255     }
    256 
    257     static boolean
    258     equalsBaseDialString (String a, String b) {
    259         return (a == null) ? (b == null) : (b != null && a.startsWith (b));
    260     }
    261 
    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 
    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
    299     public String getOrigDialString(){
    300         return mDialString;
    301     }
    302 
    303     @Override
    304     public ImsPhoneCall getCall() {
    305         return mParent;
    306     }
    307 
    308     @Override
    309     public long getDisconnectTime() {
    310         return mDisconnectTime;
    311     }
    312 
    313     @Override
    314     public long getHoldingStartTime() {
    315         return mHoldingStartTime;
    316     }
    317 
    318     @Override
    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 
    328     public void setDisconnectCause(int cause) {
    329         mCause = cause;
    330     }
    331 
    332     @Override
    333     public String getVendorDisconnectCause() {
    334       return null;
    335     }
    336 
    337     public ImsPhoneCallTracker getOwner () {
    338         return mOwner;
    339     }
    340 
    341     @Override
    342     public ImsPhoneCall.State getState() {
    343         if (mDisconnected) {
    344             return ImsPhoneCall.State.DISCONNECTED;
    345         } else {
    346             return super.getState();
    347         }
    348     }
    349 
    350     @Override
    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
    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
    377     public void separate() throws CallStateException {
    378         throw new CallStateException ("not supported");
    379     }
    380 
    381     @Override
    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
    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
    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
    428     onHangupLocal() {
    429         mCause = DisconnectCause.LOCAL;
    430     }
    431 
    432     /** Called when the connection has been disconnected */
    433     @Override
    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 
    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
    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
    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
    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
    531     protected void finalize() {
    532         releaseWakeLock();
    533     }
    534 
    535     private void
    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      */
    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
    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
    620     acquireWakeLock() {
    621         Rlog.d(LOG_TAG, "acquireWakeLock");
    622         mPartialWakeLock.acquire();
    623     }
    624 
    625     void
    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 
    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
    647     public int getNumberPresentation() {
    648         return mNumberPresentation;
    649     }
    650 
    651     @Override
    652     public UUSInfo getUUSInfo() {
    653         return mUusInfo;
    654     }
    655 
    656     @Override
    657     public Connection getOrigConnection() {
    658         return null;
    659     }
    660 
    661     @Override
    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
    675     public synchronized boolean isConferenceHost() {
    676         return mImsCall != null && mImsCall.isConferenceHost();
    677     }
    678 
    679     @Override
    680     public boolean isMemberOfPeerConference() {
    681         return !isConferenceHost();
    682     }
    683 
    684     public synchronized ImsCall getImsCall() {
    685         return mImsCall;
    686     }
    687 
    688     public synchronized void setImsCall(ImsCall imsCall) {
    689         mImsCall = imsCall;
    690     }
    691 
    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      */
    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
    737     public int getPreciseDisconnectCause() {
    738         return mPreciseDisconnectCause;
    739     }
    740 
    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
    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      */
    772     public void setConferenceConnectTime(long conferenceConnectTime) {
    773         mConferenceConnectTime = conferenceConnectTime;
    774     }
    775 
    776     /**
    777      * @return The conference connect time.
    778      */
    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      */
    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      */
    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 
    968     private void updateVideoState(int newVideoState) {
    969         if (mImsVideoCallProviderWrapper != null) {
    970             mImsVideoCallProviderWrapper.onVideoStateChanged(newVideoState);
    971         }
    972         setVideoState(newVideoState);
    973     }
    974 
    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      */
    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 
    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 
   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 
   1024     public boolean hasRttTextStream() {
   1025         return mRttTextStream != null;
   1026     }
   1027 
   1028     public boolean isRttEnabledForCall() {
   1029         return mIsRttEnabledForCall;
   1030     }
   1031 
   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.
   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      */
   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      */
   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 
   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      */
   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
   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
   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      */
   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
   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      */
   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      */
   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      */
   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      */
   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      */
   1301     public void handleMergeComplete() {
   1302         mIsMergeInProcess = false;
   1303         onConnectionEvent(android.telecom.Connection.EVENT_MERGE_COMPLETE, null);
   1304     }
   1305 
   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 
   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 
   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