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