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