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