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