| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony.imsphone; |
| |
| import android.content.Context; |
| import android.net.Uri; |
| import android.os.AsyncResult; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Messenger; |
| import android.os.PersistableBundle; |
| import android.os.PowerManager; |
| import android.os.Registrant; |
| import android.os.SystemClock; |
| import android.telecom.VideoProfile; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.DisconnectCause; |
| import android.telephony.PhoneNumberUtils; |
| import android.telephony.Rlog; |
| import android.telephony.ServiceState; |
| import android.telephony.ims.ImsCallProfile; |
| import android.telephony.ims.ImsStreamMediaProfile; |
| import android.text.TextUtils; |
| |
| import com.android.ims.ImsCall; |
| import com.android.ims.ImsException; |
| import com.android.ims.internal.ImsVideoCallProviderWrapper; |
| import com.android.internal.telephony.CallStateException; |
| import com.android.internal.telephony.Connection; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.PhoneConstants; |
| import com.android.internal.telephony.UUSInfo; |
| import com.android.internal.telephony.metrics.TelephonyMetrics; |
| |
| import java.util.Objects; |
| |
| /** |
| * {@hide} |
| */ |
| public class ImsPhoneConnection extends Connection implements |
| ImsVideoCallProviderWrapper.ImsVideoProviderWrapperCallback { |
| |
| private static final String LOG_TAG = "ImsPhoneConnection"; |
| private static final boolean DBG = true; |
| |
| //***** Instance Variables |
| |
| private ImsPhoneCallTracker mOwner; |
| private ImsPhoneCall mParent; |
| private ImsCall mImsCall; |
| private Bundle mExtras = new Bundle(); |
| private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance(); |
| |
| private boolean mDisconnected; |
| |
| /* |
| int mIndex; // index in ImsPhoneCallTracker.connections[], -1 if unassigned |
| // The GSM index is 1 + this |
| */ |
| |
| /* |
| * These time/timespan values are based on System.currentTimeMillis(), |
| * i.e., "wall clock" time. |
| */ |
| private long mDisconnectTime; |
| |
| private UUSInfo mUusInfo; |
| private Handler mHandler; |
| private Messenger mHandlerMessenger; |
| |
| private PowerManager.WakeLock mPartialWakeLock; |
| |
| // The cached connect time of the connection when it turns into a conference. |
| private long mConferenceConnectTime = 0; |
| |
| // The cached delay to be used between DTMF tones fetched from carrier config. |
| private int mDtmfToneDelay = 0; |
| |
| private boolean mIsEmergency = false; |
| |
| /** |
| * Used to indicate that video state changes detected by |
| * {@link #updateMediaCapabilities(ImsCall)} should be ignored. When a video state change from |
| * unpaused to paused occurs, we set this flag and then update the existing video state when |
| * new {@link #onReceiveSessionModifyResponse(int, VideoProfile, VideoProfile)} callbacks come |
| * in. When the video un-pauses we continue receiving the video state updates. |
| */ |
| private boolean mShouldIgnoreVideoStateChanges = false; |
| |
| private ImsVideoCallProviderWrapper mImsVideoCallProviderWrapper; |
| |
| private int mPreciseDisconnectCause = 0; |
| |
| private ImsRttTextHandler mRttTextHandler; |
| private android.telecom.Connection.RttTextStream mRttTextStream; |
| // This reflects the RTT status as reported to us by the IMS stack via the media profile. |
| private boolean mIsRttEnabledForCall = false; |
| |
| /** |
| * Used to indicate that this call is in the midst of being merged into a conference. |
| */ |
| private boolean mIsMergeInProcess = false; |
| |
| /** |
| * Used as an override to determine whether video is locally available for this call. |
| * This allows video availability to be overridden in the case that the modem says video is |
| * currently available, but mobile data is off and the carrier is metering data for video |
| * calls. |
| */ |
| private boolean mIsLocalVideoCapable = true; |
| |
| // Store the current audio codec |
| private int mAudioCodec = ImsStreamMediaProfile.AUDIO_QUALITY_NONE; |
| |
| //***** Event Constants |
| private static final int EVENT_DTMF_DONE = 1; |
| private static final int EVENT_PAUSE_DONE = 2; |
| private static final int EVENT_NEXT_POST_DIAL = 3; |
| private static final int EVENT_WAKE_LOCK_TIMEOUT = 4; |
| private static final int EVENT_DTMF_DELAY_DONE = 5; |
| |
| //***** Constants |
| private static final int PAUSE_DELAY_MILLIS = 3 * 1000; |
| private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; |
| |
| //***** Inner Classes |
| |
| class MyHandler extends Handler { |
| MyHandler(Looper l) {super(l);} |
| |
| @Override |
| public void |
| handleMessage(Message msg) { |
| |
| switch (msg.what) { |
| case EVENT_NEXT_POST_DIAL: |
| case EVENT_DTMF_DELAY_DONE: |
| case EVENT_PAUSE_DONE: |
| processNextPostDialChar(); |
| break; |
| case EVENT_WAKE_LOCK_TIMEOUT: |
| releaseWakeLock(); |
| break; |
| case EVENT_DTMF_DONE: |
| // We may need to add a delay specified by carrier between DTMF tones that are |
| // sent out. |
| mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE), |
| mDtmfToneDelay); |
| break; |
| } |
| } |
| } |
| |
| //***** Constructors |
| |
| /** This is probably an MT call */ |
| public ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct, |
| ImsPhoneCall parent, boolean isUnknown) { |
| super(PhoneConstants.PHONE_TYPE_IMS); |
| createWakeLock(phone.getContext()); |
| acquireWakeLock(); |
| |
| mOwner = ct; |
| mHandler = new MyHandler(mOwner.getLooper()); |
| mHandlerMessenger = new Messenger(mHandler); |
| mImsCall = imsCall; |
| |
| if ((imsCall != null) && (imsCall.getCallProfile() != null)) { |
| mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI); |
| mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA); |
| mNumberPresentation = ImsCallProfile.OIRToPresentation( |
| imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR)); |
| mCnapNamePresentation = ImsCallProfile.OIRToPresentation( |
| imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); |
| updateMediaCapabilities(imsCall); |
| } else { |
| mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; |
| mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN; |
| } |
| |
| mIsIncoming = !isUnknown; |
| mCreateTime = System.currentTimeMillis(); |
| mUusInfo = null; |
| |
| // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally |
| // in the ImsPhoneConnection. This isn't going to inform any listeners (since the original |
| // connection is not likely to be associated with a TelephonyConnection yet). |
| updateExtras(imsCall); |
| |
| mParent = parent; |
| mParent.attach(this, |
| (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING)); |
| |
| fetchDtmfToneDelay(phone); |
| |
| if (phone.getContext().getResources().getBoolean( |
| com.android.internal.R.bool.config_use_voip_mode_for_ims)) { |
| setAudioModeIsVoip(true); |
| } |
| } |
| |
| /** This is an MO call, created when dialing */ |
| public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct, |
| ImsPhoneCall parent, boolean isEmergency) { |
| super(PhoneConstants.PHONE_TYPE_IMS); |
| createWakeLock(phone.getContext()); |
| acquireWakeLock(); |
| |
| mOwner = ct; |
| mHandler = new MyHandler(mOwner.getLooper()); |
| |
| mDialString = dialString; |
| |
| mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); |
| mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); |
| |
| //mIndex = -1; |
| |
| mIsIncoming = false; |
| mCnapName = null; |
| mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; |
| mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; |
| mCreateTime = System.currentTimeMillis(); |
| |
| mParent = parent; |
| parent.attachFake(this, ImsPhoneCall.State.DIALING); |
| |
| mIsEmergency = isEmergency; |
| |
| fetchDtmfToneDelay(phone); |
| |
| if (phone.getContext().getResources().getBoolean( |
| com.android.internal.R.bool.config_use_voip_mode_for_ims)) { |
| setAudioModeIsVoip(true); |
| } |
| } |
| |
| public void dispose() { |
| } |
| |
| static boolean |
| equalsHandlesNulls (Object a, Object b) { |
| return (a == null) ? (b == null) : a.equals (b); |
| } |
| |
| static boolean |
| equalsBaseDialString (String a, String b) { |
| return (a == null) ? (b == null) : (b != null && a.startsWith (b)); |
| } |
| |
| private int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) { |
| Rlog.i(LOG_TAG, "applyLocalCallCapabilities - localProfile = " + localProfile); |
| capabilities = removeCapability(capabilities, |
| Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL); |
| |
| if (!mIsLocalVideoCapable) { |
| Rlog.i(LOG_TAG, "applyLocalCallCapabilities - disabling video (overidden)"); |
| return capabilities; |
| } |
| switch (localProfile.mCallType) { |
| case ImsCallProfile.CALL_TYPE_VT: |
| // Fall-through |
| case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE: |
| capabilities = addCapability(capabilities, |
| Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL); |
| break; |
| } |
| return capabilities; |
| } |
| |
| private static int applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities) { |
| Rlog.w(LOG_TAG, "applyRemoteCallCapabilities - remoteProfile = "+remoteProfile); |
| capabilities = removeCapability(capabilities, |
| Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); |
| |
| switch (remoteProfile.mCallType) { |
| case ImsCallProfile.CALL_TYPE_VT: |
| // fall-through |
| case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE: |
| capabilities = addCapability(capabilities, |
| Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); |
| break; |
| } |
| return capabilities; |
| } |
| |
| @Override |
| public String getOrigDialString(){ |
| return mDialString; |
| } |
| |
| @Override |
| public ImsPhoneCall getCall() { |
| return mParent; |
| } |
| |
| @Override |
| public long getDisconnectTime() { |
| return mDisconnectTime; |
| } |
| |
| @Override |
| public long getHoldingStartTime() { |
| return mHoldingStartTime; |
| } |
| |
| @Override |
| public long getHoldDurationMillis() { |
| if (getState() != ImsPhoneCall.State.HOLDING) { |
| // If not holding, return 0 |
| return 0; |
| } else { |
| return SystemClock.elapsedRealtime() - mHoldingStartTime; |
| } |
| } |
| |
| public void setDisconnectCause(int cause) { |
| mCause = cause; |
| } |
| |
| @Override |
| public String getVendorDisconnectCause() { |
| return null; |
| } |
| |
| public ImsPhoneCallTracker getOwner () { |
| return mOwner; |
| } |
| |
| @Override |
| public ImsPhoneCall.State getState() { |
| if (mDisconnected) { |
| return ImsPhoneCall.State.DISCONNECTED; |
| } else { |
| return super.getState(); |
| } |
| } |
| |
| @Override |
| public void deflect(String number) throws CallStateException { |
| if (mParent.getState().isRinging()) { |
| try { |
| if (mImsCall != null) { |
| mImsCall.deflect(number); |
| } else { |
| throw new CallStateException("no valid ims call to deflect"); |
| } |
| } catch (ImsException e) { |
| throw new CallStateException("cannot deflect call"); |
| } |
| } else { |
| throw new CallStateException("phone not ringing"); |
| } |
| } |
| |
| @Override |
| public void hangup() throws CallStateException { |
| if (!mDisconnected) { |
| mOwner.hangup(this); |
| } else { |
| throw new CallStateException ("disconnected"); |
| } |
| } |
| |
| @Override |
| public void separate() throws CallStateException { |
| throw new CallStateException ("not supported"); |
| } |
| |
| @Override |
| public void proceedAfterWaitChar() { |
| if (mPostDialState != PostDialState.WAIT) { |
| Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " |
| + "getPostDialState() to be WAIT but was " + mPostDialState); |
| return; |
| } |
| |
| setPostDialState(PostDialState.STARTED); |
| |
| processNextPostDialChar(); |
| } |
| |
| @Override |
| public void proceedAfterWildChar(String str) { |
| if (mPostDialState != PostDialState.WILD) { |
| Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " |
| + "getPostDialState() to be WILD but was " + mPostDialState); |
| return; |
| } |
| |
| setPostDialState(PostDialState.STARTED); |
| |
| // make a new postDialString, with the wild char replacement string |
| // at the beginning, followed by the remaining postDialString. |
| |
| StringBuilder buf = new StringBuilder(str); |
| buf.append(mPostDialString.substring(mNextPostDialChar)); |
| mPostDialString = buf.toString(); |
| mNextPostDialChar = 0; |
| if (Phone.DEBUG_PHONE) { |
| Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " + |
| mPostDialString); |
| } |
| |
| processNextPostDialChar(); |
| } |
| |
| @Override |
| public void cancelPostDial() { |
| setPostDialState(PostDialState.CANCELLED); |
| } |
| |
| /** |
| * Called when this Connection is being hung up locally (eg, user pressed "end") |
| */ |
| void |
| onHangupLocal() { |
| mCause = DisconnectCause.LOCAL; |
| } |
| |
| /** Called when the connection has been disconnected */ |
| @Override |
| public boolean onDisconnect(int cause) { |
| Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); |
| if (mCause != DisconnectCause.LOCAL || cause == DisconnectCause.INCOMING_REJECTED) { |
| mCause = cause; |
| } |
| return onDisconnect(); |
| } |
| |
| public boolean onDisconnect() { |
| boolean changed = false; |
| |
| if (!mDisconnected) { |
| //mIndex = -1; |
| |
| mDisconnectTime = System.currentTimeMillis(); |
| mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; |
| mDisconnected = true; |
| |
| mOwner.mPhone.notifyDisconnect(this); |
| notifyDisconnect(mCause); |
| |
| if (mParent != null) { |
| changed = mParent.connectionDisconnected(this); |
| } else { |
| Rlog.d(LOG_TAG, "onDisconnect: no parent"); |
| } |
| synchronized (this) { |
| if (mRttTextHandler != null) { |
| mRttTextHandler.tearDown(); |
| } |
| if (mImsCall != null) mImsCall.close(); |
| mImsCall = null; |
| } |
| } |
| releaseWakeLock(); |
| return changed; |
| } |
| |
| /** |
| * An incoming or outgoing call has connected |
| */ |
| void |
| onConnectedInOrOut() { |
| mConnectTime = System.currentTimeMillis(); |
| mConnectTimeReal = SystemClock.elapsedRealtime(); |
| mDuration = 0; |
| |
| if (Phone.DEBUG_PHONE) { |
| Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime); |
| } |
| |
| if (!mIsIncoming) { |
| // outgoing calls only |
| processNextPostDialChar(); |
| } |
| releaseWakeLock(); |
| } |
| |
| /*package*/ void |
| onStartedHolding() { |
| mHoldingStartTime = SystemClock.elapsedRealtime(); |
| } |
| /** |
| * Performs the appropriate action for a post-dial char, but does not |
| * notify application. returns false if the character is invalid and |
| * should be ignored |
| */ |
| private boolean |
| processPostDialChar(char c) { |
| if (PhoneNumberUtils.is12Key(c)) { |
| Message dtmfComplete = mHandler.obtainMessage(EVENT_DTMF_DONE); |
| dtmfComplete.replyTo = mHandlerMessenger; |
| mOwner.sendDtmf(c, dtmfComplete); |
| } else if (c == PhoneNumberUtils.PAUSE) { |
| // From TS 22.101: |
| // It continues... |
| // Upon the called party answering the UE shall send the DTMF digits |
| // automatically to the network after a delay of 3 seconds( 20 ). |
| // The digits shall be sent according to the procedures and timing |
| // specified in 3GPP TS 24.008 [13]. The first occurrence of the |
| // "DTMF Control Digits Separator" shall be used by the ME to |
| // distinguish between the addressing digits (i.e. the phone number) |
| // and the DTMF digits. Upon subsequent occurrences of the |
| // separator, |
| // the UE shall pause again for 3 seconds ( 20 ) before sending |
| // any further DTMF digits. |
| mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), |
| PAUSE_DELAY_MILLIS); |
| } else if (c == PhoneNumberUtils.WAIT) { |
| setPostDialState(PostDialState.WAIT); |
| } else if (c == PhoneNumberUtils.WILD) { |
| setPostDialState(PostDialState.WILD); |
| } else { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected void finalize() { |
| releaseWakeLock(); |
| } |
| |
| private void |
| processNextPostDialChar() { |
| char c = 0; |
| Registrant postDialHandler; |
| |
| if (mPostDialState == PostDialState.CANCELLED) { |
| //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail"); |
| return; |
| } |
| |
| if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) { |
| setPostDialState(PostDialState.COMPLETE); |
| |
| // notifyMessage.arg1 is 0 on complete |
| c = 0; |
| } else { |
| boolean isValid; |
| |
| setPostDialState(PostDialState.STARTED); |
| |
| c = mPostDialString.charAt(mNextPostDialChar++); |
| |
| isValid = processPostDialChar(c); |
| |
| if (!isValid) { |
| // Will call processNextPostDialChar |
| mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); |
| // Don't notify application |
| Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); |
| return; |
| } |
| } |
| |
| notifyPostDialListenersNextChar(c); |
| |
| // TODO: remove the following code since the handler no longer executes anything. |
| postDialHandler = mOwner.mPhone.getPostDialHandler(); |
| |
| Message notifyMessage; |
| |
| if (postDialHandler != null |
| && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { |
| // The AsyncResult.result is the Connection object |
| PostDialState state = mPostDialState; |
| AsyncResult ar = AsyncResult.forMessage(notifyMessage); |
| ar.result = this; |
| ar.userObj = state; |
| |
| // arg1 is the character that was/is being processed |
| notifyMessage.arg1 = c; |
| |
| //Rlog.v(LOG_TAG, |
| // "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); |
| notifyMessage.sendToTarget(); |
| } |
| } |
| |
| /** |
| * Set post dial state and acquire wake lock while switching to "started" |
| * state, the wake lock will be released if state switches out of "started" |
| * state or after WAKE_LOCK_TIMEOUT_MILLIS. |
| * @param s new PostDialState |
| */ |
| private void setPostDialState(PostDialState s) { |
| if (mPostDialState != PostDialState.STARTED |
| && s == PostDialState.STARTED) { |
| acquireWakeLock(); |
| Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); |
| mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); |
| } else if (mPostDialState == PostDialState.STARTED |
| && s != PostDialState.STARTED) { |
| mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); |
| releaseWakeLock(); |
| } |
| mPostDialState = s; |
| notifyPostDialListeners(); |
| } |
| |
| private void |
| createWakeLock(Context context) { |
| PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); |
| mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); |
| } |
| |
| private void |
| acquireWakeLock() { |
| Rlog.d(LOG_TAG, "acquireWakeLock"); |
| mPartialWakeLock.acquire(); |
| } |
| |
| void |
| releaseWakeLock() { |
| if (mPartialWakeLock != null) { |
| synchronized (mPartialWakeLock) { |
| if (mPartialWakeLock.isHeld()) { |
| Rlog.d(LOG_TAG, "releaseWakeLock"); |
| mPartialWakeLock.release(); |
| } |
| } |
| } |
| } |
| |
| private void fetchDtmfToneDelay(Phone phone) { |
| CarrierConfigManager configMgr = (CarrierConfigManager) |
| phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); |
| PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId()); |
| if (b != null) { |
| mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT); |
| } |
| } |
| |
| @Override |
| public int getNumberPresentation() { |
| return mNumberPresentation; |
| } |
| |
| @Override |
| public UUSInfo getUUSInfo() { |
| return mUusInfo; |
| } |
| |
| @Override |
| public Connection getOrigConnection() { |
| return null; |
| } |
| |
| @Override |
| public synchronized boolean isMultiparty() { |
| return mImsCall != null && mImsCall.isMultiparty(); |
| } |
| |
| /** |
| * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the |
| * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this |
| * {@link ImsCall} is a member of a conference hosted on another device. |
| * |
| * @return {@code true} if this call is the origin of the conference call it is a member of, |
| * {@code false} otherwise. |
| */ |
| @Override |
| public synchronized boolean isConferenceHost() { |
| return mImsCall != null && mImsCall.isConferenceHost(); |
| } |
| |
| @Override |
| public boolean isMemberOfPeerConference() { |
| return !isConferenceHost(); |
| } |
| |
| public synchronized ImsCall getImsCall() { |
| return mImsCall; |
| } |
| |
| public synchronized void setImsCall(ImsCall imsCall) { |
| mImsCall = imsCall; |
| } |
| |
| public void changeParent(ImsPhoneCall parent) { |
| mParent = parent; |
| } |
| |
| /** |
| * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been |
| * changed, and {@code false} otherwise. |
| */ |
| public boolean update(ImsCall imsCall, ImsPhoneCall.State state) { |
| if (state == ImsPhoneCall.State.ACTIVE) { |
| // If the state of the call is active, but there is a pending request to the RIL to hold |
| // the call, we will skip this update. This is really a signalling delay or failure |
| // from the RIL, but we will prevent it from going through as we will end up erroneously |
| // making this call active when really it should be on hold. |
| if (imsCall.isPendingHold()) { |
| Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping"); |
| return false; |
| } |
| |
| if (mParent.getState().isRinging() || mParent.getState().isDialing()) { |
| onConnectedInOrOut(); |
| } |
| |
| if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) { |
| //mForegroundCall should be IDLE |
| //when accepting WAITING call |
| //before accept WAITING call, |
| //the ACTIVE call should be held ahead |
| mParent.detach(this); |
| mParent = mOwner.mForegroundCall; |
| mParent.attach(this); |
| } |
| } else if (state == ImsPhoneCall.State.HOLDING) { |
| onStartedHolding(); |
| } |
| |
| boolean updateParent = mParent.update(this, imsCall, state); |
| boolean updateAddressDisplay = updateAddressDisplay(imsCall); |
| boolean updateMediaCapabilities = updateMediaCapabilities(imsCall); |
| boolean updateExtras = updateExtras(imsCall); |
| |
| return updateParent || updateAddressDisplay || updateMediaCapabilities || updateExtras; |
| } |
| |
| @Override |
| public int getPreciseDisconnectCause() { |
| return mPreciseDisconnectCause; |
| } |
| |
| public void setPreciseDisconnectCause(int cause) { |
| mPreciseDisconnectCause = cause; |
| } |
| |
| /** |
| * Notifies this Connection of a request to disconnect a participant of the conference managed |
| * by the connection. |
| * |
| * @param endpoint the {@link android.net.Uri} of the participant to disconnect. |
| */ |
| @Override |
| public void onDisconnectConferenceParticipant(Uri endpoint) { |
| ImsCall imsCall = getImsCall(); |
| if (imsCall == null) { |
| return; |
| } |
| try { |
| imsCall.removeParticipants(new String[]{endpoint.toString()}); |
| } catch (ImsException e) { |
| // No session in place -- no change |
| Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+ |
| "Failed to disconnect endpoint = " + endpoint); |
| } |
| } |
| |
| /** |
| * Sets the conference connect time. Used when an {@code ImsConference} is created to out of |
| * this phone connection. |
| * |
| * @param conferenceConnectTime The conference connect time. |
| */ |
| public void setConferenceConnectTime(long conferenceConnectTime) { |
| mConferenceConnectTime = conferenceConnectTime; |
| } |
| |
| /** |
| * @return The conference connect time. |
| */ |
| public long getConferenceConnectTime() { |
| return mConferenceConnectTime; |
| } |
| |
| /** |
| * Check for a change in the address display related fields for the {@link ImsCall}, and |
| * update the {@link ImsPhoneConnection} with this information. |
| * |
| * @param imsCall The call to check for changes in address display fields. |
| * @return Whether the address display fields have been changed. |
| */ |
| public boolean updateAddressDisplay(ImsCall imsCall) { |
| if (imsCall == null) { |
| return false; |
| } |
| |
| boolean changed = false; |
| ImsCallProfile callProfile = imsCall.getCallProfile(); |
| if (callProfile != null && isIncoming()) { |
| // Only look for changes to the address for incoming calls. The originating identity |
| // can change for outgoing calls due to, for example, a call being forwarded to |
| // voicemail. This address change does not need to be presented to the user. |
| String address = callProfile.getCallExtra(ImsCallProfile.EXTRA_OI); |
| String name = callProfile.getCallExtra(ImsCallProfile.EXTRA_CNA); |
| int nump = ImsCallProfile.OIRToPresentation( |
| callProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR)); |
| int namep = ImsCallProfile.OIRToPresentation( |
| callProfile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); |
| if (Phone.DEBUG_PHONE) { |
| Rlog.d(LOG_TAG, "updateAddressDisplay: callId = " + getTelecomCallId() |
| + " address = " + Rlog.pii(LOG_TAG, address) + " name = " |
| + Rlog.pii(LOG_TAG, name) + " nump = " + nump + " namep = " + namep); |
| } |
| if (!mIsMergeInProcess) { |
| // Only process changes to the name and address when a merge is not in process. |
| // When call A initiated a merge with call B to form a conference C, there is a |
| // point in time when the ImsCall transfers the conference call session into A, |
| // at which point the ImsConferenceController creates the conference in Telecom. |
| // For some carriers C will have a unique conference URI address. Swapping the |
| // conference session into A, which is about to be disconnected, to be logged to |
| // the call log using the conference address. To prevent this we suppress updates |
| // to the call address while a merge is in process. |
| if (!equalsBaseDialString(mAddress, address)) { |
| mAddress = address; |
| changed = true; |
| } |
| if (TextUtils.isEmpty(name)) { |
| if (!TextUtils.isEmpty(mCnapName)) { |
| mCnapName = ""; |
| changed = true; |
| } |
| } else if (!name.equals(mCnapName)) { |
| mCnapName = name; |
| changed = true; |
| } |
| if (mNumberPresentation != nump) { |
| mNumberPresentation = nump; |
| changed = true; |
| } |
| if (mCnapNamePresentation != namep) { |
| mCnapNamePresentation = namep; |
| changed = true; |
| } |
| } |
| } |
| return changed; |
| } |
| |
| /** |
| * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and |
| * update the {@link ImsPhoneConnection} with this information. |
| * |
| * @param imsCall The call to check for changes in media capabilities. |
| * @return Whether the media capabilities have been changed. |
| */ |
| public boolean updateMediaCapabilities(ImsCall imsCall) { |
| if (imsCall == null) { |
| return false; |
| } |
| |
| boolean changed = false; |
| |
| try { |
| // The actual call profile (negotiated between local and peer). |
| ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile(); |
| |
| if (negotiatedCallProfile != null) { |
| int oldVideoState = getVideoState(); |
| int newVideoState = ImsCallProfile |
| .getVideoStateFromImsCallProfile(negotiatedCallProfile); |
| |
| if (oldVideoState != newVideoState) { |
| // The video state has changed. See also code in onReceiveSessionModifyResponse |
| // below. When the video enters a paused state, subsequent changes to the video |
| // state will not be reported by the modem. In onReceiveSessionModifyResponse |
| // we will be updating the current video state while paused to include any |
| // changes the modem reports via the video provider. When the video enters an |
| // unpaused state, we will resume passing the video states from the modem as is. |
| if (VideoProfile.isPaused(oldVideoState) && |
| !VideoProfile.isPaused(newVideoState)) { |
| // Video entered un-paused state; recognize updates from now on; we want to |
| // ensure that the new un-paused state is propagated to Telecom, so change |
| // this now. |
| mShouldIgnoreVideoStateChanges = false; |
| } |
| |
| if (!mShouldIgnoreVideoStateChanges) { |
| updateVideoState(newVideoState); |
| changed = true; |
| } else { |
| Rlog.d(LOG_TAG, "updateMediaCapabilities - ignoring video state change " + |
| "due to paused state."); |
| } |
| |
| if (!VideoProfile.isPaused(oldVideoState) && |
| VideoProfile.isPaused(newVideoState)) { |
| // Video entered pause state; ignore updates until un-paused. We do this |
| // after setVideoState is called above to ensure Telecom is notified that |
| // the device has entered paused state. |
| mShouldIgnoreVideoStateChanges = true; |
| } |
| } |
| |
| if (negotiatedCallProfile.mMediaProfile != null) { |
| mIsRttEnabledForCall = negotiatedCallProfile.mMediaProfile.isRttCall(); |
| |
| if (mIsRttEnabledForCall && mRttTextHandler == null) { |
| Rlog.d(LOG_TAG, "updateMediaCapabilities -- turning RTT on, profile=" |
| + negotiatedCallProfile); |
| startRttTextProcessing(); |
| onRttInitiated(); |
| changed = true; |
| } else if (!mIsRttEnabledForCall && mRttTextHandler != null) { |
| Rlog.d(LOG_TAG, "updateMediaCapabilities -- turning RTT off, profile=" |
| + negotiatedCallProfile); |
| mRttTextHandler.tearDown(); |
| mRttTextHandler = null; |
| onRttTerminated(); |
| changed = true; |
| } |
| } |
| } |
| |
| // Check for a change in the capabilities for the call and update |
| // {@link ImsPhoneConnection} with this information. |
| int capabilities = getConnectionCapabilities(); |
| |
| // Use carrier config to determine if downgrading directly to audio-only is supported. |
| if (mOwner.isCarrierDowngradeOfVtCallSupported()) { |
| capabilities = addCapability(capabilities, |
| Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE | |
| Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL); |
| } else { |
| capabilities = removeCapability(capabilities, |
| Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE | |
| Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL); |
| } |
| |
| // Get the current local call capabilities which might be voice or video or both. |
| ImsCallProfile localCallProfile = imsCall.getLocalCallProfile(); |
| Rlog.v(LOG_TAG, "update localCallProfile=" + localCallProfile); |
| if (localCallProfile != null) { |
| capabilities = applyLocalCallCapabilities(localCallProfile, capabilities); |
| } |
| |
| // Get the current remote call capabilities which might be voice or video or both. |
| ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile(); |
| Rlog.v(LOG_TAG, "update remoteCallProfile=" + remoteCallProfile); |
| if (remoteCallProfile != null) { |
| capabilities = applyRemoteCallCapabilities(remoteCallProfile, capabilities); |
| } |
| if (getConnectionCapabilities() != capabilities) { |
| setConnectionCapabilities(capabilities); |
| changed = true; |
| } |
| |
| if (!mOwner.isViLteDataMetered()) { |
| Rlog.v(LOG_TAG, "data is not metered"); |
| } else { |
| if (mImsVideoCallProviderWrapper != null) { |
| mImsVideoCallProviderWrapper.setIsVideoEnabled( |
| hasCapabilities(Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL)); |
| } |
| } |
| |
| // Metrics for audio codec |
| if (localCallProfile != null |
| && localCallProfile.mMediaProfile.mAudioQuality != mAudioCodec) { |
| mAudioCodec = localCallProfile.mMediaProfile.mAudioQuality; |
| mMetrics.writeAudioCodecIms(mOwner.mPhone.getPhoneId(), imsCall.getCallSession()); |
| } |
| |
| int newAudioQuality = |
| getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile); |
| if (getAudioQuality() != newAudioQuality) { |
| setAudioQuality(newAudioQuality); |
| changed = true; |
| } |
| } catch (ImsException e) { |
| // No session in place -- no change |
| } |
| |
| return changed; |
| } |
| |
| private void updateVideoState(int newVideoState) { |
| if (mImsVideoCallProviderWrapper != null) { |
| mImsVideoCallProviderWrapper.onVideoStateChanged(newVideoState); |
| } |
| setVideoState(newVideoState); |
| } |
| |
| |
| /** |
| * Send a RTT upgrade request to the remote party. |
| * @param textStream RTT text stream to use |
| */ |
| public void startRtt(android.telecom.Connection.RttTextStream textStream) { |
| ImsCall imsCall = getImsCall(); |
| if (imsCall != null) { |
| getImsCall().sendRttModifyRequest(true); |
| setCurrentRttTextStream(textStream); |
| } |
| } |
| |
| /** |
| * Terminate the current RTT session. |
| */ |
| public void stopRtt() { |
| getImsCall().sendRttModifyRequest(false); |
| } |
| |
| /** |
| * Sends the user's response to a remotely-issued RTT upgrade request |
| * |
| * @param textStream A valid {@link android.telecom.Connection.RttTextStream} if the user |
| * accepts, {@code null} if not. |
| */ |
| public void sendRttModifyResponse(android.telecom.Connection.RttTextStream textStream) { |
| boolean accept = textStream != null; |
| ImsCall imsCall = getImsCall(); |
| |
| if (imsCall != null) { |
| imsCall.sendRttModifyResponse(accept); |
| if (accept) { |
| setCurrentRttTextStream(textStream); |
| } else { |
| Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections"); |
| } |
| } |
| } |
| |
| public void onRttMessageReceived(String message) { |
| synchronized (this) { |
| if (mRttTextHandler == null) { |
| Rlog.w(LOG_TAG, "onRttMessageReceived: RTT text handler not available." |
| + " Attempting to create one."); |
| if (mRttTextStream == null) { |
| Rlog.e(LOG_TAG, "onRttMessageReceived:" |
| + " Unable to process incoming message. No textstream available"); |
| return; |
| } |
| createRttTextHandler(); |
| } |
| } |
| mRttTextHandler.sendToInCall(message); |
| } |
| |
| public void setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream) { |
| synchronized (this) { |
| mRttTextStream = rttTextStream; |
| if (mRttTextHandler == null && mIsRttEnabledForCall) { |
| Rlog.i(LOG_TAG, "setCurrentRttTextStream: Creating a text handler"); |
| createRttTextHandler(); |
| } |
| } |
| } |
| |
| public boolean hasRttTextStream() { |
| return mRttTextStream != null; |
| } |
| |
| public boolean isRttEnabledForCall() { |
| return mIsRttEnabledForCall; |
| } |
| |
| public void startRttTextProcessing() { |
| synchronized (this) { |
| if (mRttTextStream == null) { |
| Rlog.w(LOG_TAG, "startRttTextProcessing: no RTT text stream. Ignoring."); |
| return; |
| } |
| if (mRttTextHandler != null) { |
| Rlog.w(LOG_TAG, "startRttTextProcessing: RTT text handler already exists"); |
| return; |
| } |
| createRttTextHandler(); |
| } |
| } |
| |
| // Make sure to synchronize on ImsPhoneConnection.this before calling. |
| private void createRttTextHandler() { |
| mRttTextHandler = new ImsRttTextHandler(Looper.getMainLooper(), |
| (message) -> { |
| ImsCall imsCall = getImsCall(); |
| if (imsCall != null) { |
| imsCall.sendRttMessage(message); |
| } |
| }); |
| mRttTextHandler.initialize(mRttTextStream); |
| } |
| |
| /** |
| * Updates the IMS call rat based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}. |
| * |
| * @param extras The ImsCallProfile extras. |
| */ |
| private void updateImsCallRatFromExtras(Bundle extras) { |
| if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE) || |
| extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) { |
| |
| ImsCall call = getImsCall(); |
| int callTech = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN; |
| if (call != null) { |
| callTech = call.getRadioTechnology(); |
| } |
| |
| // Report any changes for call tech change |
| setCallRadioTech(callTech); |
| } |
| } |
| |
| private void updateEmergencyCallFromExtras(Bundle extras) { |
| if (extras.getBoolean(ImsCallProfile.EXTRA_EMERGENCY_CALL)) { |
| setIsNetworkIdentifiedEmergencyCall(true); |
| } |
| } |
| |
| /** |
| * Check for a change in call extras of {@link ImsCall}, and |
| * update the {@link ImsPhoneConnection} accordingly. |
| * |
| * @param imsCall The call to check for changes in extras. |
| * @return Whether the extras fields have been changed. |
| */ |
| boolean updateExtras(ImsCall imsCall) { |
| if (imsCall == null) { |
| return false; |
| } |
| |
| final ImsCallProfile callProfile = imsCall.getCallProfile(); |
| final Bundle extras = callProfile != null ? callProfile.mCallExtras : null; |
| if (extras == null && DBG) { |
| Rlog.d(LOG_TAG, "Call profile extras are null."); |
| } |
| |
| final boolean changed = !areBundlesEqual(extras, mExtras); |
| if (changed) { |
| updateImsCallRatFromExtras(extras); |
| updateEmergencyCallFromExtras(extras); |
| mExtras.clear(); |
| mExtras.putAll(extras); |
| setConnectionExtras(mExtras); |
| } |
| return changed; |
| } |
| |
| private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { |
| if (extras == null || newExtras == null) { |
| return extras == newExtras; |
| } |
| |
| if (extras.size() != newExtras.size()) { |
| return false; |
| } |
| |
| for(String key : extras.keySet()) { |
| if (key != null) { |
| final Object value = extras.get(key); |
| final Object newValue = newExtras.get(key); |
| if (!Objects.equals(value, newValue)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote |
| * {@link ImsCallProfile}. Indicate a HD audio call if the local stream profile |
| * is AMR_WB, EVRC_WB, EVS_WB, EVS_SWB, EVS_FB and |
| * there is no remote restrict cause. |
| * |
| * @param localCallProfile The local call profile. |
| * @param remoteCallProfile The remote call profile. |
| * @return The audio quality. |
| */ |
| private int getAudioQualityFromCallProfile( |
| ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) { |
| if (localCallProfile == null || remoteCallProfile == null |
| || localCallProfile.mMediaProfile == null) { |
| return AUDIO_QUALITY_STANDARD; |
| } |
| |
| final boolean isEvsCodecHighDef = (localCallProfile.mMediaProfile.mAudioQuality |
| == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB |
| || localCallProfile.mMediaProfile.mAudioQuality |
| == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB |
| || localCallProfile.mMediaProfile.mAudioQuality |
| == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB); |
| |
| final boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality |
| == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB |
| || localCallProfile.mMediaProfile.mAudioQuality |
| == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB |
| || isEvsCodecHighDef) |
| && remoteCallProfile.getRestrictCause() == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE; |
| return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD; |
| } |
| |
| /** |
| * Provides a string representation of the {@link ImsPhoneConnection}. Primarily intended for |
| * use in log statements. |
| * |
| * @return String representation of call. |
| */ |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("[ImsPhoneConnection objId: "); |
| sb.append(System.identityHashCode(this)); |
| sb.append(" telecomCallID: "); |
| sb.append(getTelecomCallId()); |
| sb.append(" address: "); |
| sb.append(Rlog.pii(LOG_TAG, getAddress())); |
| sb.append(" ImsCall: "); |
| synchronized (this) { |
| if (mImsCall == null) { |
| sb.append("null"); |
| } else { |
| sb.append(mImsCall); |
| } |
| } |
| sb.append("]"); |
| return sb.toString(); |
| } |
| |
| @Override |
| public void setVideoProvider(android.telecom.Connection.VideoProvider videoProvider) { |
| super.setVideoProvider(videoProvider); |
| |
| if (videoProvider instanceof ImsVideoCallProviderWrapper) { |
| mImsVideoCallProviderWrapper = (ImsVideoCallProviderWrapper) videoProvider; |
| } |
| } |
| |
| /** |
| * Indicates whether current phone connection is emergency or not |
| * @return boolean: true if emergency, false otherwise |
| */ |
| protected boolean isEmergency() { |
| return mIsEmergency; |
| } |
| |
| /** |
| * Handles notifications from the {@link ImsVideoCallProviderWrapper} of session modification |
| * responses received. |
| * |
| * @param status The status of the original request. |
| * @param requestProfile The requested video profile. |
| * @param responseProfile The response upon video profile. |
| */ |
| @Override |
| public void onReceiveSessionModifyResponse(int status, VideoProfile requestProfile, |
| VideoProfile responseProfile) { |
| if (status == android.telecom.Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS && |
| mShouldIgnoreVideoStateChanges) { |
| int currentVideoState = getVideoState(); |
| int newVideoState = responseProfile.getVideoState(); |
| |
| // If the current video state is paused, the modem will not send us any changes to |
| // the TX and RX bits of the video state. Until the video is un-paused we will |
| // "fake out" the video state by applying the changes that the modem reports via a |
| // response. |
| |
| // First, find out whether there was a change to the TX or RX bits: |
| int changedBits = currentVideoState ^ newVideoState; |
| changedBits &= VideoProfile.STATE_BIDIRECTIONAL; |
| if (changedBits == 0) { |
| // No applicable change, bail out. |
| return; |
| } |
| |
| // Turn off any existing bits that changed. |
| currentVideoState &= ~(changedBits & currentVideoState); |
| // Turn on any new bits that turned on. |
| currentVideoState |= changedBits & newVideoState; |
| |
| Rlog.d(LOG_TAG, "onReceiveSessionModifyResponse : received " + |
| VideoProfile.videoStateToString(requestProfile.getVideoState()) + |
| " / " + |
| VideoProfile.videoStateToString(responseProfile.getVideoState()) + |
| " while paused ; sending new videoState = " + |
| VideoProfile.videoStateToString(currentVideoState)); |
| setVideoState(currentVideoState); |
| } |
| } |
| |
| /** |
| * Issues a request to pause the video using {@link VideoProfile#STATE_PAUSED} from a source |
| * other than the InCall UI. |
| * |
| * @param source The source of the pause request. |
| */ |
| public void pauseVideo(int source) { |
| if (mImsVideoCallProviderWrapper == null) { |
| return; |
| } |
| |
| mImsVideoCallProviderWrapper.pauseVideo(getVideoState(), source); |
| } |
| |
| /** |
| * Issues a request to resume the video using {@link VideoProfile#STATE_PAUSED} from a source |
| * other than the InCall UI. |
| * |
| * @param source The source of the resume request. |
| */ |
| public void resumeVideo(int source) { |
| if (mImsVideoCallProviderWrapper == null) { |
| return; |
| } |
| |
| mImsVideoCallProviderWrapper.resumeVideo(getVideoState(), source); |
| } |
| |
| /** |
| * Determines if a specified source has issued a pause request. |
| * |
| * @param source The source. |
| * @return {@code true} if the source issued a pause request, {@code false} otherwise. |
| */ |
| public boolean wasVideoPausedFromSource(int source) { |
| if (mImsVideoCallProviderWrapper == null) { |
| return false; |
| } |
| |
| return mImsVideoCallProviderWrapper.wasVideoPausedFromSource(source); |
| } |
| |
| /** |
| * Mark the call as in the process of being merged and inform the UI of the merge start. |
| */ |
| public void handleMergeStart() { |
| mIsMergeInProcess = true; |
| onConnectionEvent(android.telecom.Connection.EVENT_MERGE_START, null); |
| } |
| |
| /** |
| * Mark the call as done merging and inform the UI of the merge start. |
| */ |
| public void handleMergeComplete() { |
| mIsMergeInProcess = false; |
| onConnectionEvent(android.telecom.Connection.EVENT_MERGE_COMPLETE, null); |
| } |
| |
| public void changeToPausedState() { |
| int newVideoState = getVideoState() | VideoProfile.STATE_PAUSED; |
| Rlog.i(LOG_TAG, "ImsPhoneConnection: changeToPausedState - setting paused bit; " |
| + "newVideoState=" + VideoProfile.videoStateToString(newVideoState)); |
| updateVideoState(newVideoState); |
| mShouldIgnoreVideoStateChanges = true; |
| } |
| |
| public void changeToUnPausedState() { |
| int newVideoState = getVideoState() & ~VideoProfile.STATE_PAUSED; |
| Rlog.i(LOG_TAG, "ImsPhoneConnection: changeToUnPausedState - unsetting paused bit; " |
| + "newVideoState=" + VideoProfile.videoStateToString(newVideoState)); |
| updateVideoState(newVideoState); |
| mShouldIgnoreVideoStateChanges = false; |
| } |
| |
| public void setLocalVideoCapable(boolean isVideoEnabled) { |
| mIsLocalVideoCapable = isVideoEnabled; |
| Rlog.i(LOG_TAG, "setLocalVideoCapable: mIsLocalVideoCapable = " + mIsLocalVideoCapable |
| + "; updating local video availability."); |
| updateMediaCapabilities(getImsCall()); |
| } |
| } |