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