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