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