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