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