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