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