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