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