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