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