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