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