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