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