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