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