ImsPhoneConnection.java revision fa88fc1de20ddb819975d78c98e4c0d6b27e225a
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 17package com.android.internal.telephony.imsphone; 18 19import android.content.Context; 20import android.net.Uri; 21import android.os.AsyncResult; 22import android.os.Bundle; 23import android.os.Handler; 24import android.os.Looper; 25import android.os.Message; 26import android.os.PersistableBundle; 27import android.os.PowerManager; 28import android.os.Registrant; 29import android.os.SystemClock; 30import android.telecom.VideoProfile; 31import android.telephony.CarrierConfigManager; 32import android.telephony.DisconnectCause; 33import android.telephony.PhoneNumberUtils; 34import android.telephony.Rlog; 35import android.telephony.ServiceState; 36import android.text.TextUtils; 37 38import com.android.ims.ImsException; 39import com.android.ims.ImsStreamMediaProfile; 40import com.android.ims.internal.ImsVideoCallProviderWrapper; 41import com.android.internal.telephony.CallStateException; 42import com.android.internal.telephony.Connection; 43import com.android.internal.telephony.Phone; 44import com.android.internal.telephony.PhoneConstants; 45import com.android.internal.telephony.UUSInfo; 46 47import com.android.ims.ImsCall; 48import com.android.ims.ImsCallProfile; 49 50import java.util.Objects; 51 52/** 53 * {@hide} 54 */ 55public class ImsPhoneConnection extends Connection implements 56 ImsVideoCallProviderWrapper.ImsVideoProviderWrapperCallback { 57 58 private static final String LOG_TAG = "ImsPhoneConnection"; 59 private static final boolean DBG = true; 60 61 //***** Instance Variables 62 63 private ImsPhoneCallTracker mOwner; 64 private ImsPhoneCall mParent; 65 private ImsCall mImsCall; 66 private Bundle mExtras = new Bundle(); 67 68 private boolean mDisconnected; 69 private boolean mDisconnecting = false; 70 71 /* 72 int mIndex; // index in ImsPhoneCallTracker.connections[], -1 if unassigned 73 // The GSM index is 1 + this 74 */ 75 76 /* 77 * These time/timespan values are based on System.currentTimeMillis(), 78 * i.e., "wall clock" time. 79 */ 80 private long mDisconnectTime; 81 82 private UUSInfo mUusInfo; 83 private Handler mHandler; 84 85 private PowerManager.WakeLock mPartialWakeLock; 86 87 // The cached connect time of the connection when it turns into a conference. 88 private long mConferenceConnectTime = 0; 89 90 // The cached delay to be used between DTMF tones fetched from carrier config. 91 private int mDtmfToneDelay = 0; 92 93 private boolean mIsEmergency = false; 94 95 /** 96 * Used to indicate that video state changes detected by 97 * {@link #updateMediaCapabilities(ImsCall)} should be ignored. When a video state change from 98 * unpaused to paused occurs, we set this flag and then update the existing video state when 99 * new {@link #onReceiveSessionModifyResponse(int, VideoProfile, VideoProfile)} callbacks come 100 * in. When the video un-pauses we continue receiving the video state updates. 101 */ 102 private boolean mShouldIgnoreVideoStateChanges = false; 103 104 /** 105 * Used to indicate whether the wifi state is based on 106 * {@link com.android.ims.ImsConnectionStateListener# 107 * onFeatureCapabilityChanged(int, int[], int[])} callbacks, or values received via the 108 * {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra. Util we receive a value via the extras, 109 * we will use the wifi state based on the {@code onFeatureCapabilityChanged}. Once a value 110 * is received via the extras, we will prefer those values going forward. 111 */ 112 private boolean mIsWifiStateFromExtras = false; 113 114 private int mPreciseDisconnectCause = 0; 115 116 private ImsRttTextHandler mRttTextHandler; 117 private android.telecom.Connection.RttTextStream mRttTextStream; 118 119 //***** Event Constants 120 private static final int EVENT_DTMF_DONE = 1; 121 private static final int EVENT_PAUSE_DONE = 2; 122 private static final int EVENT_NEXT_POST_DIAL = 3; 123 private static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 124 private static final int EVENT_DTMF_DELAY_DONE = 5; 125 126 //***** Constants 127 private static final int PAUSE_DELAY_MILLIS = 3 * 1000; 128 private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 129 130 //***** Inner Classes 131 132 class MyHandler extends Handler { 133 MyHandler(Looper l) {super(l);} 134 135 @Override 136 public void 137 handleMessage(Message msg) { 138 139 switch (msg.what) { 140 case EVENT_NEXT_POST_DIAL: 141 case EVENT_DTMF_DELAY_DONE: 142 case EVENT_PAUSE_DONE: 143 processNextPostDialChar(); 144 break; 145 case EVENT_WAKE_LOCK_TIMEOUT: 146 releaseWakeLock(); 147 break; 148 case EVENT_DTMF_DONE: 149 // We may need to add a delay specified by carrier between DTMF tones that are 150 // sent out. 151 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE), 152 mDtmfToneDelay); 153 break; 154 } 155 } 156 } 157 158 //***** Constructors 159 160 /** This is probably an MT call */ 161 public ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct, 162 ImsPhoneCall parent, boolean isUnknown) { 163 super(PhoneConstants.PHONE_TYPE_IMS); 164 createWakeLock(phone.getContext()); 165 acquireWakeLock(); 166 167 mOwner = ct; 168 mHandler = new MyHandler(mOwner.getLooper()); 169 mImsCall = imsCall; 170 171 if ((imsCall != null) && (imsCall.getCallProfile() != null)) { 172 mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI); 173 mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA); 174 mNumberPresentation = ImsCallProfile.OIRToPresentation( 175 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 176 mCnapNamePresentation = ImsCallProfile.OIRToPresentation( 177 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 178 updateMediaCapabilities(imsCall); 179 } else { 180 mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 181 mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN; 182 } 183 184 mIsIncoming = !isUnknown; 185 mCreateTime = System.currentTimeMillis(); 186 mUusInfo = null; 187 188 updateWifiState(); 189 190 // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally 191 // in the ImsPhoneConnection. This isn't going to inform any listeners (since the original 192 // connection is not likely to be associated with a TelephonyConnection yet). 193 updateExtras(imsCall); 194 195 mParent = parent; 196 mParent.attach(this, 197 (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING)); 198 199 fetchDtmfToneDelay(phone); 200 201 if (phone.getContext().getResources().getBoolean( 202 com.android.internal.R.bool.config_use_voip_mode_for_ims)) { 203 setAudioModeIsVoip(true); 204 } 205 } 206 207 /** This is an MO call, created when dialing */ 208 public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct, 209 ImsPhoneCall parent, boolean isEmergency) { 210 super(PhoneConstants.PHONE_TYPE_IMS); 211 createWakeLock(phone.getContext()); 212 acquireWakeLock(); 213 214 mOwner = ct; 215 mHandler = new MyHandler(mOwner.getLooper()); 216 217 mDialString = dialString; 218 219 mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 220 mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 221 222 //mIndex = -1; 223 224 mIsIncoming = false; 225 mCnapName = null; 226 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 227 mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 228 mCreateTime = System.currentTimeMillis(); 229 230 mParent = parent; 231 parent.attachFake(this, ImsPhoneCall.State.DIALING); 232 233 mIsEmergency = isEmergency; 234 235 fetchDtmfToneDelay(phone); 236 237 if (phone.getContext().getResources().getBoolean( 238 com.android.internal.R.bool.config_use_voip_mode_for_ims)) { 239 setAudioModeIsVoip(true); 240 } 241 } 242 243 public void dispose() { 244 } 245 246 static boolean 247 equalsHandlesNulls (Object a, Object b) { 248 return (a == null) ? (b == null) : a.equals (b); 249 } 250 251 private static int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) { 252 Rlog.w(LOG_TAG, "applyLocalCallCapabilities - localProfile = "+localProfile); 253 capabilities = removeCapability(capabilities, 254 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL); 255 256 switch (localProfile.mCallType) { 257 case ImsCallProfile.CALL_TYPE_VT: 258 // Fall-through 259 case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE: 260 capabilities = addCapability(capabilities, 261 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL); 262 break; 263 } 264 return capabilities; 265 } 266 267 private static int applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities) { 268 Rlog.w(LOG_TAG, "applyRemoteCallCapabilities - remoteProfile = "+remoteProfile); 269 capabilities = removeCapability(capabilities, 270 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 271 272 switch (remoteProfile.mCallType) { 273 case ImsCallProfile.CALL_TYPE_VT: 274 // fall-through 275 case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE: 276 capabilities = addCapability(capabilities, 277 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 278 break; 279 } 280 return capabilities; 281 } 282 283 @Override 284 public String getOrigDialString(){ 285 return mDialString; 286 } 287 288 @Override 289 public ImsPhoneCall getCall() { 290 return mParent; 291 } 292 293 @Override 294 public long getDisconnectTime() { 295 return mDisconnectTime; 296 } 297 298 @Override 299 public long getHoldingStartTime() { 300 return mHoldingStartTime; 301 } 302 303 @Override 304 public long getHoldDurationMillis() { 305 if (getState() != ImsPhoneCall.State.HOLDING) { 306 // If not holding, return 0 307 return 0; 308 } else { 309 return SystemClock.elapsedRealtime() - mHoldingStartTime; 310 } 311 } 312 313 public void setDisconnectCause(int cause) { 314 mCause = cause; 315 } 316 317 @Override 318 public String getVendorDisconnectCause() { 319 return null; 320 } 321 322 public ImsPhoneCallTracker getOwner () { 323 return mOwner; 324 } 325 326 @Override 327 public ImsPhoneCall.State getState() { 328 if (mDisconnected) { 329 return ImsPhoneCall.State.DISCONNECTED; 330 } else { 331 return super.getState(); 332 } 333 } 334 335 @Override 336 public void hangup() throws CallStateException { 337 if (!mDisconnected) { 338 mOwner.hangup(this); 339 } else { 340 throw new CallStateException ("disconnected"); 341 } 342 } 343 344 @Override 345 public void separate() throws CallStateException { 346 throw new CallStateException ("not supported"); 347 } 348 349 @Override 350 public void proceedAfterWaitChar() { 351 if (mPostDialState != PostDialState.WAIT) { 352 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 353 + "getPostDialState() to be WAIT but was " + mPostDialState); 354 return; 355 } 356 357 setPostDialState(PostDialState.STARTED); 358 359 processNextPostDialChar(); 360 } 361 362 @Override 363 public void proceedAfterWildChar(String str) { 364 if (mPostDialState != PostDialState.WILD) { 365 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 366 + "getPostDialState() to be WILD but was " + mPostDialState); 367 return; 368 } 369 370 setPostDialState(PostDialState.STARTED); 371 372 // make a new postDialString, with the wild char replacement string 373 // at the beginning, followed by the remaining postDialString. 374 375 StringBuilder buf = new StringBuilder(str); 376 buf.append(mPostDialString.substring(mNextPostDialChar)); 377 mPostDialString = buf.toString(); 378 mNextPostDialChar = 0; 379 if (Phone.DEBUG_PHONE) { 380 Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " + 381 mPostDialString); 382 } 383 384 processNextPostDialChar(); 385 } 386 387 @Override 388 public void cancelPostDial() { 389 setPostDialState(PostDialState.CANCELLED); 390 } 391 392 /** 393 * Called when this Connection is being hung up locally (eg, user pressed "end") 394 */ 395 void 396 onHangupLocal() { 397 mDisconnecting = true; 398 mCause = DisconnectCause.LOCAL; 399 } 400 401 /** 402 * Return whether or not this connection is DISCONNECTING and waiting for a signal from the 403 * modem to disconnect. 404 */ 405 boolean isDisconnecting() { 406 return mDisconnecting; 407 } 408 409 /** Called when the connection has been disconnected */ 410 @Override 411 public boolean onDisconnect(int cause) { 412 Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); 413 if (mCause != DisconnectCause.LOCAL || cause == DisconnectCause.INCOMING_REJECTED) { 414 mCause = cause; 415 } 416 return onDisconnect(); 417 } 418 419 public boolean onDisconnect() { 420 boolean changed = false; 421 422 if (!mDisconnected) { 423 //mIndex = -1; 424 425 mDisconnectTime = System.currentTimeMillis(); 426 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 427 mDisconnecting = false; 428 mDisconnected = true; 429 430 mOwner.mPhone.notifyDisconnect(this); 431 432 if (mParent != null) { 433 changed = mParent.connectionDisconnected(this); 434 } else { 435 Rlog.d(LOG_TAG, "onDisconnect: no parent"); 436 } 437 if (mImsCall != null) mImsCall.close(); 438 mImsCall = null; 439 } 440 releaseWakeLock(); 441 return changed; 442 } 443 444 /** 445 * An incoming or outgoing call has connected 446 */ 447 void 448 onConnectedInOrOut() { 449 mConnectTime = System.currentTimeMillis(); 450 mConnectTimeReal = SystemClock.elapsedRealtime(); 451 mDuration = 0; 452 453 if (Phone.DEBUG_PHONE) { 454 Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime); 455 } 456 457 if (!mIsIncoming) { 458 // outgoing calls only 459 processNextPostDialChar(); 460 } 461 releaseWakeLock(); 462 } 463 464 /*package*/ void 465 onStartedHolding() { 466 mHoldingStartTime = SystemClock.elapsedRealtime(); 467 } 468 /** 469 * Performs the appropriate action for a post-dial char, but does not 470 * notify application. returns false if the character is invalid and 471 * should be ignored 472 */ 473 private boolean 474 processPostDialChar(char c) { 475 if (PhoneNumberUtils.is12Key(c)) { 476 mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 477 } else if (c == PhoneNumberUtils.PAUSE) { 478 // From TS 22.101: 479 // It continues... 480 // Upon the called party answering the UE shall send the DTMF digits 481 // automatically to the network after a delay of 3 seconds( 20 ). 482 // The digits shall be sent according to the procedures and timing 483 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 484 // "DTMF Control Digits Separator" shall be used by the ME to 485 // distinguish between the addressing digits (i.e. the phone number) 486 // and the DTMF digits. Upon subsequent occurrences of the 487 // separator, 488 // the UE shall pause again for 3 seconds ( 20 ) before sending 489 // any further DTMF digits. 490 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 491 PAUSE_DELAY_MILLIS); 492 } else if (c == PhoneNumberUtils.WAIT) { 493 setPostDialState(PostDialState.WAIT); 494 } else if (c == PhoneNumberUtils.WILD) { 495 setPostDialState(PostDialState.WILD); 496 } else { 497 return false; 498 } 499 500 return true; 501 } 502 503 @Override 504 protected void finalize() { 505 releaseWakeLock(); 506 } 507 508 private void 509 processNextPostDialChar() { 510 char c = 0; 511 Registrant postDialHandler; 512 513 if (mPostDialState == PostDialState.CANCELLED) { 514 //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 515 return; 516 } 517 518 if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) { 519 setPostDialState(PostDialState.COMPLETE); 520 521 // notifyMessage.arg1 is 0 on complete 522 c = 0; 523 } else { 524 boolean isValid; 525 526 setPostDialState(PostDialState.STARTED); 527 528 c = mPostDialString.charAt(mNextPostDialChar++); 529 530 isValid = processPostDialChar(c); 531 532 if (!isValid) { 533 // Will call processNextPostDialChar 534 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 535 // Don't notify application 536 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); 537 return; 538 } 539 } 540 541 notifyPostDialListenersNextChar(c); 542 543 // TODO: remove the following code since the handler no longer executes anything. 544 postDialHandler = mOwner.mPhone.getPostDialHandler(); 545 546 Message notifyMessage; 547 548 if (postDialHandler != null 549 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 550 // The AsyncResult.result is the Connection object 551 PostDialState state = mPostDialState; 552 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 553 ar.result = this; 554 ar.userObj = state; 555 556 // arg1 is the character that was/is being processed 557 notifyMessage.arg1 = c; 558 559 //Rlog.v(LOG_TAG, 560 // "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 561 notifyMessage.sendToTarget(); 562 } 563 } 564 565 /** 566 * Set post dial state and acquire wake lock while switching to "started" 567 * state, the wake lock will be released if state switches out of "started" 568 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 569 * @param s new PostDialState 570 */ 571 private void setPostDialState(PostDialState s) { 572 if (mPostDialState != PostDialState.STARTED 573 && s == PostDialState.STARTED) { 574 acquireWakeLock(); 575 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 576 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 577 } else if (mPostDialState == PostDialState.STARTED 578 && s != PostDialState.STARTED) { 579 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 580 releaseWakeLock(); 581 } 582 mPostDialState = s; 583 notifyPostDialListeners(); 584 } 585 586 private void 587 createWakeLock(Context context) { 588 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 589 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 590 } 591 592 private void 593 acquireWakeLock() { 594 Rlog.d(LOG_TAG, "acquireWakeLock"); 595 mPartialWakeLock.acquire(); 596 } 597 598 void 599 releaseWakeLock() { 600 synchronized(mPartialWakeLock) { 601 if (mPartialWakeLock.isHeld()) { 602 Rlog.d(LOG_TAG, "releaseWakeLock"); 603 mPartialWakeLock.release(); 604 } 605 } 606 } 607 608 private void fetchDtmfToneDelay(Phone phone) { 609 CarrierConfigManager configMgr = (CarrierConfigManager) 610 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 611 PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId()); 612 if (b != null) { 613 mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT); 614 } 615 } 616 617 @Override 618 public int getNumberPresentation() { 619 return mNumberPresentation; 620 } 621 622 @Override 623 public UUSInfo getUUSInfo() { 624 return mUusInfo; 625 } 626 627 @Override 628 public Connection getOrigConnection() { 629 return null; 630 } 631 632 @Override 633 public boolean isMultiparty() { 634 return mImsCall != null && mImsCall.isMultiparty(); 635 } 636 637 /** 638 * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the 639 * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this 640 * {@link ImsCall} is a member of a conference hosted on another device. 641 * 642 * @return {@code true} if this call is the origin of the conference call it is a member of, 643 * {@code false} otherwise. 644 */ 645 @Override 646 public boolean isConferenceHost() { 647 if (mImsCall == null) { 648 return false; 649 } 650 return mImsCall.isConferenceHost(); 651 } 652 653 @Override 654 public boolean isMemberOfPeerConference() { 655 return !isConferenceHost(); 656 } 657 658 public ImsCall getImsCall() { 659 return mImsCall; 660 } 661 662 public void setImsCall(ImsCall imsCall) { 663 mImsCall = imsCall; 664 } 665 666 public void changeParent(ImsPhoneCall parent) { 667 mParent = parent; 668 } 669 670 /** 671 * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been 672 * changed, and {@code false} otherwise. 673 */ 674 public boolean update(ImsCall imsCall, ImsPhoneCall.State state) { 675 if (state == ImsPhoneCall.State.ACTIVE) { 676 // If the state of the call is active, but there is a pending request to the RIL to hold 677 // the call, we will skip this update. This is really a signalling delay or failure 678 // from the RIL, but we will prevent it from going through as we will end up erroneously 679 // making this call active when really it should be on hold. 680 if (imsCall.isPendingHold()) { 681 Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping"); 682 return false; 683 } 684 685 if (mParent.getState().isRinging() || mParent.getState().isDialing()) { 686 onConnectedInOrOut(); 687 } 688 689 if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) { 690 //mForegroundCall should be IDLE 691 //when accepting WAITING call 692 //before accept WAITING call, 693 //the ACTIVE call should be held ahead 694 mParent.detach(this); 695 mParent = mOwner.mForegroundCall; 696 mParent.attach(this); 697 } 698 } else if (state == ImsPhoneCall.State.HOLDING) { 699 onStartedHolding(); 700 } 701 702 boolean updateParent = mParent.update(this, imsCall, state); 703 boolean updateWifiState = updateWifiState(); 704 boolean updateAddressDisplay = updateAddressDisplay(imsCall); 705 boolean updateMediaCapabilities = updateMediaCapabilities(imsCall); 706 boolean updateExtras = updateExtras(imsCall); 707 708 return updateParent || updateWifiState || updateAddressDisplay || updateMediaCapabilities 709 || updateExtras; 710 } 711 712 @Override 713 public int getPreciseDisconnectCause() { 714 return mPreciseDisconnectCause; 715 } 716 717 public void setPreciseDisconnectCause(int cause) { 718 mPreciseDisconnectCause = cause; 719 } 720 721 /** 722 * Notifies this Connection of a request to disconnect a participant of the conference managed 723 * by the connection. 724 * 725 * @param endpoint the {@link android.net.Uri} of the participant to disconnect. 726 */ 727 @Override 728 public void onDisconnectConferenceParticipant(Uri endpoint) { 729 ImsCall imsCall = getImsCall(); 730 if (imsCall == null) { 731 return; 732 } 733 try { 734 imsCall.removeParticipants(new String[]{endpoint.toString()}); 735 } catch (ImsException e) { 736 // No session in place -- no change 737 Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+ 738 "Failed to disconnect endpoint = " + endpoint); 739 } 740 } 741 742 /** 743 * Sets the conference connect time. Used when an {@code ImsConference} is created to out of 744 * this phone connection. 745 * 746 * @param conferenceConnectTime The conference connect time. 747 */ 748 public void setConferenceConnectTime(long conferenceConnectTime) { 749 mConferenceConnectTime = conferenceConnectTime; 750 } 751 752 /** 753 * @return The conference connect time. 754 */ 755 public long getConferenceConnectTime() { 756 return mConferenceConnectTime; 757 } 758 759 /** 760 * Check for a change in the address display related fields for the {@link ImsCall}, and 761 * update the {@link ImsPhoneConnection} with this information. 762 * 763 * @param imsCall The call to check for changes in address display fields. 764 * @return Whether the address display fields have been changed. 765 */ 766 public boolean updateAddressDisplay(ImsCall imsCall) { 767 if (imsCall == null) { 768 return false; 769 } 770 771 boolean changed = false; 772 ImsCallProfile callProfile = imsCall.getCallProfile(); 773 if (callProfile != null) { 774 String address = callProfile.getCallExtra(ImsCallProfile.EXTRA_OI); 775 String name = callProfile.getCallExtra(ImsCallProfile.EXTRA_CNA); 776 int nump = ImsCallProfile.OIRToPresentation( 777 callProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 778 int namep = ImsCallProfile.OIRToPresentation( 779 callProfile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 780 if (Phone.DEBUG_PHONE) { 781 Rlog.d(LOG_TAG, "address = " + Rlog.pii(LOG_TAG, address) + " name = " + name + 782 " nump = " + nump + " namep = " + namep); 783 } 784 if(equalsHandlesNulls(mAddress, address)) { 785 mAddress = address; 786 changed = true; 787 } 788 if (TextUtils.isEmpty(name)) { 789 if (!TextUtils.isEmpty(mCnapName)) { 790 mCnapName = ""; 791 changed = true; 792 } 793 } else if (!name.equals(mCnapName)) { 794 mCnapName = name; 795 changed = true; 796 } 797 if (mNumberPresentation != nump) { 798 mNumberPresentation = nump; 799 changed = true; 800 } 801 if (mCnapNamePresentation != namep) { 802 mCnapNamePresentation = namep; 803 changed = true; 804 } 805 } 806 return changed; 807 } 808 809 /** 810 * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and 811 * update the {@link ImsPhoneConnection} with this information. 812 * 813 * @param imsCall The call to check for changes in media capabilities. 814 * @return Whether the media capabilities have been changed. 815 */ 816 public boolean updateMediaCapabilities(ImsCall imsCall) { 817 if (imsCall == null) { 818 return false; 819 } 820 821 boolean changed = false; 822 823 try { 824 // The actual call profile (negotiated between local and peer). 825 ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile(); 826 827 if (negotiatedCallProfile != null) { 828 int oldVideoState = getVideoState(); 829 int newVideoState = ImsCallProfile 830 .getVideoStateFromImsCallProfile(negotiatedCallProfile); 831 832 if (oldVideoState != newVideoState) { 833 // The video state has changed. See also code in onReceiveSessionModifyResponse 834 // below. When the video enters a paused state, subsequent changes to the video 835 // state will not be reported by the modem. In onReceiveSessionModifyResponse 836 // we will be updating the current video state while paused to include any 837 // changes the modem reports via the video provider. When the video enters an 838 // unpaused state, we will resume passing the video states from the modem as is. 839 if (VideoProfile.isPaused(oldVideoState) && 840 !VideoProfile.isPaused(newVideoState)) { 841 // Video entered un-paused state; recognize updates from now on; we want to 842 // ensure that the new un-paused state is propagated to Telecom, so change 843 // this now. 844 mShouldIgnoreVideoStateChanges = false; 845 } 846 847 if (!mShouldIgnoreVideoStateChanges) { 848 setVideoState(newVideoState); 849 changed = true; 850 } else { 851 Rlog.d(LOG_TAG, "updateMediaCapabilities - ignoring video state change " + 852 "due to paused state."); 853 } 854 855 if (!VideoProfile.isPaused(oldVideoState) && 856 VideoProfile.isPaused(newVideoState)) { 857 // Video entered pause state; ignore updates until un-paused. We do this 858 // after setVideoState is called above to ensure Telecom is notified that 859 // the device has entered paused state. 860 mShouldIgnoreVideoStateChanges = true; 861 } 862 } 863 } 864 865 // Check for a change in the capabilities for the call and update 866 // {@link ImsPhoneConnection} with this information. 867 int capabilities = getConnectionCapabilities(); 868 869 // Use carrier config to determine if downgrading directly to audio-only is supported. 870 if (mOwner.isCarrierDowngradeOfVtCallSupported()) { 871 capabilities = addCapability(capabilities, 872 Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE | 873 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL); 874 } else { 875 capabilities = removeCapability(capabilities, 876 Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE | 877 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL); 878 } 879 880 // Get the current local call capabilities which might be voice or video or both. 881 ImsCallProfile localCallProfile = imsCall.getLocalCallProfile(); 882 Rlog.v(LOG_TAG, "update localCallProfile=" + localCallProfile); 883 if (localCallProfile != null) { 884 capabilities = applyLocalCallCapabilities(localCallProfile, capabilities); 885 } 886 887 // Get the current remote call capabilities which might be voice or video or both. 888 ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile(); 889 Rlog.v(LOG_TAG, "update remoteCallProfile=" + remoteCallProfile); 890 if (remoteCallProfile != null) { 891 capabilities = applyRemoteCallCapabilities(remoteCallProfile, capabilities); 892 } 893 if (getConnectionCapabilities() != capabilities) { 894 setConnectionCapabilities(capabilities); 895 changed = true; 896 } 897 898 int newAudioQuality = 899 getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile); 900 if (getAudioQuality() != newAudioQuality) { 901 setAudioQuality(newAudioQuality); 902 changed = true; 903 } 904 } catch (ImsException e) { 905 // No session in place -- no change 906 } 907 908 return changed; 909 } 910 911 public void sendRttModifyRequest(android.telecom.Connection.RttTextStream textStream) { 912 getImsCall().sendRttModifyRequest(); 913 setCurrentRttTextStream(textStream); 914 } 915 916 /** 917 * Sends the user's response to a remotely-issued RTT upgrade request 918 * 919 * @param textStream A valid {@link android.telecom.Connection.RttTextStream} if the user 920 * accepts, {@code null} if not. 921 */ 922 public void sendRttModifyResponse(android.telecom.Connection.RttTextStream textStream) { 923 boolean accept = textStream != null; 924 ImsCall imsCall = getImsCall(); 925 926 imsCall.sendRttModifyResponse(accept); 927 if (accept) { 928 setCurrentRttTextStream(textStream); 929 startRttTextProcessing(); 930 } else { 931 Rlog.e(LOG_TAG, "sendRttModifyResponse: foreground call has no connections"); 932 } 933 } 934 935 public void onRttMessageReceived(String message) { 936 getOrCreateRttTextHandler().sendToInCall(message); 937 } 938 939 public void setCurrentRttTextStream(android.telecom.Connection.RttTextStream rttTextStream) { 940 mRttTextStream = rttTextStream; 941 } 942 943 public void startRttTextProcessing() { 944 getOrCreateRttTextHandler().initialize(mRttTextStream); 945 } 946 947 private ImsRttTextHandler getOrCreateRttTextHandler() { 948 if (mRttTextHandler != null) { 949 return mRttTextHandler; 950 } 951 mRttTextHandler = new ImsRttTextHandler(Looper.getMainLooper(), 952 (message) -> getImsCall().sendRttMessage(message)); 953 return mRttTextHandler; 954 } 955 956 /** 957 * Check for a change in the wifi state of the ImsPhoneCallTracker and update the 958 * {@link ImsPhoneConnection} with this information. 959 * 960 * @return Whether the ImsPhoneCallTracker's usage of wifi has been changed. 961 */ 962 public boolean updateWifiState() { 963 // If we've received the wifi state via the ImsCallProfile.EXTRA_CALL_RAT_TYPE extra, we 964 // will no longer use state updates which are based on the onFeatureCapabilityChanged 965 // callback. 966 if (mIsWifiStateFromExtras) { 967 return false; 968 } 969 970 Rlog.d(LOG_TAG, "updateWifiState: " + mOwner.isVowifiEnabled()); 971 if (isWifi() != mOwner.isVowifiEnabled()) { 972 setWifi(mOwner.isVowifiEnabled()); 973 return true; 974 } 975 return false; 976 } 977 978 /** 979 * Updates the wifi state based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}. 980 * The call is considered to be a WIFI call if the extra value is 981 * {@link ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}. 982 * 983 * @param extras The ImsCallProfile extras. 984 */ 985 private void updateWifiStateFromExtras(Bundle extras) { 986 if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE) || 987 extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) { 988 989 // We've received the extra indicating the radio technology, so we will continue to 990 // prefer the radio technology received via this extra going forward. 991 mIsWifiStateFromExtras = true; 992 993 ImsCall call = getImsCall(); 994 boolean isWifi = false; 995 if (call != null) { 996 isWifi = call.isWifiCall(); 997 } 998 999 // Report any changes 1000 if (isWifi() != isWifi) { 1001 setWifi(isWifi); 1002 } 1003 } 1004 } 1005 1006 /** 1007 * Check for a change in call extras of {@link ImsCall}, and 1008 * update the {@link ImsPhoneConnection} accordingly. 1009 * 1010 * @param imsCall The call to check for changes in extras. 1011 * @return Whether the extras fields have been changed. 1012 */ 1013 boolean updateExtras(ImsCall imsCall) { 1014 if (imsCall == null) { 1015 return false; 1016 } 1017 1018 final ImsCallProfile callProfile = imsCall.getCallProfile(); 1019 final Bundle extras = callProfile != null ? callProfile.mCallExtras : null; 1020 if (extras == null && DBG) { 1021 Rlog.d(LOG_TAG, "Call profile extras are null."); 1022 } 1023 1024 final boolean changed = !areBundlesEqual(extras, mExtras); 1025 if (changed) { 1026 updateWifiStateFromExtras(extras); 1027 1028 mExtras.clear(); 1029 mExtras.putAll(extras); 1030 setConnectionExtras(mExtras); 1031 } 1032 return changed; 1033 } 1034 1035 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 1036 if (extras == null || newExtras == null) { 1037 return extras == newExtras; 1038 } 1039 1040 if (extras.size() != newExtras.size()) { 1041 return false; 1042 } 1043 1044 for(String key : extras.keySet()) { 1045 if (key != null) { 1046 final Object value = extras.get(key); 1047 final Object newValue = newExtras.get(key); 1048 if (!Objects.equals(value, newValue)) { 1049 return false; 1050 } 1051 } 1052 } 1053 return true; 1054 } 1055 1056 /** 1057 * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote 1058 * {@link ImsCallProfile}. Indicate a HD audio call if the local stream profile 1059 * is AMR_WB, EVRC_WB, EVS_WB, EVS_SWB, EVS_FB and 1060 * there is no remote restrict cause. 1061 * 1062 * @param localCallProfile The local call profile. 1063 * @param remoteCallProfile The remote call profile. 1064 * @return The audio quality. 1065 */ 1066 private int getAudioQualityFromCallProfile( 1067 ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) { 1068 if (localCallProfile == null || remoteCallProfile == null 1069 || localCallProfile.mMediaProfile == null) { 1070 return AUDIO_QUALITY_STANDARD; 1071 } 1072 1073 final boolean isEvsCodecHighDef = (localCallProfile.mMediaProfile.mAudioQuality 1074 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB 1075 || localCallProfile.mMediaProfile.mAudioQuality 1076 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB 1077 || localCallProfile.mMediaProfile.mAudioQuality 1078 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB); 1079 1080 final boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality 1081 == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB 1082 || localCallProfile.mMediaProfile.mAudioQuality 1083 == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB 1084 || isEvsCodecHighDef) 1085 && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE; 1086 return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD; 1087 } 1088 1089 /** 1090 * Provides a string representation of the {@link ImsPhoneConnection}. Primarily intended for 1091 * use in log statements. 1092 * 1093 * @return String representation of call. 1094 */ 1095 @Override 1096 public String toString() { 1097 StringBuilder sb = new StringBuilder(); 1098 sb.append("[ImsPhoneConnection objId: "); 1099 sb.append(System.identityHashCode(this)); 1100 sb.append(" telecomCallID: "); 1101 sb.append(getTelecomCallId()); 1102 sb.append(" address: "); 1103 sb.append(Rlog.pii(LOG_TAG, getAddress())); 1104 sb.append(" ImsCall: "); 1105 if (mImsCall == null) { 1106 sb.append("null"); 1107 } else { 1108 sb.append(mImsCall); 1109 } 1110 sb.append("]"); 1111 return sb.toString(); 1112 } 1113 1114 /** 1115 * Indicates whether current phone connection is emergency or not 1116 * @return boolean: true if emergency, false otherwise 1117 */ 1118 protected boolean isEmergency() { 1119 return mIsEmergency; 1120 } 1121 1122 /** 1123 * Handles notifications from the {@link ImsVideoCallProviderWrapper} of session modification 1124 * responses received. 1125 * 1126 * @param status The status of the original request. 1127 * @param requestProfile The requested video profile. 1128 * @param responseProfile The response upon video profile. 1129 */ 1130 @Override 1131 public void onReceiveSessionModifyResponse(int status, VideoProfile requestProfile, 1132 VideoProfile responseProfile) { 1133 if (status == android.telecom.Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS && 1134 mShouldIgnoreVideoStateChanges) { 1135 int currentVideoState = getVideoState(); 1136 int newVideoState = responseProfile.getVideoState(); 1137 1138 // If the current video state is paused, the modem will not send us any changes to 1139 // the TX and RX bits of the video state. Until the video is un-paused we will 1140 // "fake out" the video state by applying the changes that the modem reports via a 1141 // response. 1142 1143 // First, find out whether there was a change to the TX or RX bits: 1144 int changedBits = currentVideoState ^ newVideoState; 1145 changedBits &= VideoProfile.STATE_BIDIRECTIONAL; 1146 if (changedBits == 0) { 1147 // No applicable change, bail out. 1148 return; 1149 } 1150 1151 // Turn off any existing bits that changed. 1152 currentVideoState &= ~(changedBits & currentVideoState); 1153 // Turn on any new bits that turned on. 1154 currentVideoState |= changedBits & newVideoState; 1155 1156 Rlog.d(LOG_TAG, "onReceiveSessionModifyResponse : received " + 1157 VideoProfile.videoStateToString(requestProfile.getVideoState()) + 1158 " / " + 1159 VideoProfile.videoStateToString(responseProfile.getVideoState()) + 1160 " while paused ; sending new videoState = " + 1161 VideoProfile.videoStateToString(currentVideoState)); 1162 setVideoState(currentVideoState); 1163 } 1164 } 1165} 1166