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