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