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