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