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