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