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