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