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